{"info":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","description":"<html><head></head><body><img src=\"https://pvml.com/wp-content/uploads/2024/11/img-3d-home-hero.png?t=1735745544&amp;t=1735745544\">\n\n<h1 id=\"get-started-here\">📄 Get started here!</h1>\n<h2 id=\"introduction\">Introduction</h2>\n<p>PVML is a database virtualization infrastructure that lets IT teams spin up <strong>Virtual Databases (VDBs):</strong> <strong>secure virtual environments</strong> on top of their existing data stack in seconds.</p>\n<p><strong>Not a copy, no data stored. 100% data-less.</strong></p>\n<p>Each VDB enforces <strong>fine-grained access controls</strong> such as column-level policies, row-level security (RLS), query timeout, dynamic masking, and differential privacy, <strong>independent of the capabilities of the underlying database</strong>.</p>\n<p>Every query is intercepted and transformed in real time - policies baked directly into the query - so only secure outputs ever leave the database.</p>\n<h3 id=\"plug-and-play\">Plug-and-Play</h3>\n<p>Connecting your existing databases to PVML takes seconds - just a connection string. <strong>No installations, no drivers, no agents, no data copying.</strong></p>\n<p>Once connected, VDBs are instantly consumable by <strong>any client</strong> through standard protocols, including:</p>\n<ul>\n<li><p><strong>ODBC/JDBC</strong> - for BI tools (Tableau, PowerBI, etc.)</p>\n</li>\n<li><p><strong>MCP</strong> (Model Context Protocol) - for GenAI applications and platforms (ChatGPT, LangChain, etc.)</p>\n</li>\n<li><p><strong>API -</strong> for custom applications, agents, services, and programmatic integrations via REST/HTTP interfaces</p>\n</li>\n</ul>\n<h3 id=\"how-it-works\">How it works</h3>\n<p>A VDB consists of 2 components:</p>\n<ul>\n<li><p><em><strong>PVML Proxy Engine</strong></em> (reverse-proxy), matches any underlying database technology and can decode any query.</p>\n</li>\n<li><p><em><strong>PVML Runtime Authorization Engine</strong></em> (compiler), dialect-agnostic, can reconstruct any query and re-build it into a secure query. This means that guardrails enforcement is done solely as <strong>pre-processing</strong> via query transformation, making the solution <strong>efficient, secure, and optimized</strong> (under 3ms overhead).</p>\n</li>\n</ul>\n<p>A VDB <strong>does not ingest or store raw data</strong>. It acts like a database from a <strong>connectivity &amp; enforcement</strong> perspective:</p>\n<ul>\n<li><p>Adheres to the protocol of the physical database</p>\n</li>\n<li><p>Exposes standard interfaces such as ODBC/JDBC, MCP, API</p>\n</li>\n<li><p>Independently enforcing guardrails at runtime.</p>\n</li>\n<li><p>Built-in user-level authentication (oAuth for MCP)</p>\n</li>\n<li><p>Every data interaction is automatically audited</p>\n</li>\n</ul>\n<h3 id=\"whats-our-edge\">What's Our Edge?</h3>\n<p>We deliver <strong>bionic security at enterprise scale</strong>, bringing deterministic security to the non-deterministic GenAI era by combining advanced guardrails with runtime enforcement, so every AI interaction with enterprise data is governed and auditable by design.</p>\n<h4 id=\"🤖❌-our-solution-is-100-llm-free\">🤖❌ Our solution is 100% LLM free</h4>\n<p>We don't rely on AI to enforce privacy and security. Every query is routed through our dedicated Runtime Authorization Engine, which deterministically ensures results adhere to your VDB guardrails.</p>\n<p>The result: the tradeoff IT has always faced - between security and business enablement - is gone. Your teams can connect AI on real data without compromise.</p>\n<h3 id=\"impact\">Impact</h3>\n<ul>\n<li><p><strong>Deterministic Security by Design:</strong> GenAI requires security that is not probabilistic, but predictable and auditable.</p>\n</li>\n<li><p><strong>No Workflow Changes:</strong> Legacy code from 30 years ago works as-is. Modern AI agents connect just the same.</p>\n</li>\n<li><p><strong>Enterprise-Grade Scale &amp; Performance:</strong> No more encryption or synthetic data. Get live, secure access with sub-3 ms latency.</p>\n</li>\n<li><p><strong>No Data Movement or Duplication:</strong> No siloed copies, no stale extracts. On-demand Virtual Databases for any use case.</p>\n</li>\n<li><p><strong>Context-Aware Access at Scale:</strong> Infinite virtual databases over the same underlying data, enabling context-specific access identities at scale.</p>\n</li>\n</ul>\n<h2 id=\"guardrails\">Guardrails</h2>\n<p>IT teams can now unify permissions across all data sources through a centralized infrastructure plane. This eliminates inconsistent enforcement across systems, reduces operational complexity, and applies security controls uniformly at enterprise scale.</p>\n<p>Our <strong>Runtime Authorization Engine</strong> enables highly granular access control, including:</p>\n<ol>\n<li><p>Column level scoping</p>\n</li>\n<li><p>Dynamic row level security (RLS)</p>\n</li>\n<li><p>Dynamic masking</p>\n</li>\n<li><p>Differential Privacy</p>\n</li>\n<li><p>Aggregation functions only (Privacy)</p>\n</li>\n<li><p>Load control (timeout and row limit)</p>\n</li>\n</ol>\n<h3 id=\"differential-privacy---pvmls-proprietary-algorithms\">Differential Privacy - PVML's Proprietary Algorithms</h3>\n<p>PVML offers a robust data protection technology called Differential Privacy to protect data outputs with mathematical guarantees of individual privacy. This concept is unique since it requires no alterations to the underlying data, ensuring privacy is enforced at the output level of the analysis.</p>\n<h2 id=\"audit--visibility\">Audit &amp; Visibility</h2>\n<img src=\"https://content.pstmn.io/d3213f81-f0b8-4f57-865d-2bf124171348/QXVkaXQucG5n\">\n\n<p>Each virtual DB provides full visibility over all data interactions. You can track queries of all users, success/failure rates, run duration and even the free text questions used in agentic pipelines.</p>\n<p>We also provide <strong>System Audit</strong> to monitor workspace-level actions, such as who changed a policy, a VDB or a user's access, and when.</p>\n<h3 id=\"security-and-compliance-applications\">Security and Compliance Applications</h3>\n<p>Security and compliance teams gain structured audit and security logs for every data interaction, including query metadata, user metadata, and policy violation attempts. The logs are in a standard JSON format, so you can send them to the tools you already use: your SIEM, ELK, CloudWatch, Datadog or any other monitoring platform. Use them there for investigations, threat detection and compliance reports.</p>\n<h3 id=\"product-and-business-applications\">Product and Business Applications</h3>\n<p>Data and AI teams gain visibility into how users and AI agents interact with data through free-text questions, latency metrics, and success/failure tracking. Teams can identify recurring questions, repeated failures, broken workflows, and inefficient queries to improve agent behavior, optimize applications, refine prompts, and prioritize product improvements based on real usage patterns.</p>\n<h2 id=\"how-does-pvml-speed-up-my-genai-initiatives\">How Does PVML Speed Up My GenAI Initiatives?</h2>\n<ul>\n<li><p><strong>Security:</strong> VDB guardrails can be tailored to compliance and governance requirements, enabling initiatives previously blocked by security constraints.</p>\n</li>\n<li><p><strong>Connectivity:</strong> PVML automatically provides an MCP server and an API for each virtual database, plug-n-play into any agent building platform (e.g., Anthropic Claude, OpenAI ChatGPT, Microsoft Copilot Studio), AI application (e.g., Cursor, Claude Code) or internal solution (e.g., built on LangChain).</p>\n</li>\n<li><p><strong>Performance &amp; Accuracy:</strong> Each VDB can be optimally scoped for the GenAI initiative and includes a semantic layer for describing metadata and business context to LLMs, improving query generation, reasoning quality, response reliability, and overall application performance.</p>\n</li>\n</ul>\n<h2 id=\"deploy-anywhere\">Deploy Anywhere</h2>\n<p>PVML supports flexible deployment across cloud and on-premise environments.</p>\n<h3 id=\"cloud-deployment-saas\">Cloud Deployment (SaaS)</h3>\n<ul>\n<li><p>Cloud-agnostic (Azure, AWS, GCP, Gov Cloud)</p>\n</li>\n<li><p>Available on Google Cloud Marketplace &amp; AWS Marketplace</p>\n</li>\n<li><p>Supports VPC peering and dedicated tenant configurations</p>\n</li>\n</ul>\n<h3 id=\"on-premise-deployment\">On-Premise Deployment</h3>\n<ul>\n<li><p>Deploy on your existing Kubernetes infrastructure</p>\n</li>\n<li><p>No dependency on external services: no external APIs, no LLMs, no GPUs required.</p>\n</li>\n<li><p>Supports multi-datacenter setups and high-availability configurations</p>\n</li>\n</ul>\n<h2 id=\"how-is-pvml-compliant\">How is PVML Compliant?</h2>\n<p>PVML provides cutting-edge data protection solutions powered by our proprietary Differential Privacy (DP) technology, which stands out in the EU review of anonymization methods: the <a href=\"https://pvml.com/blog/differential-privacy-what-is-art-29-wp-really-saying-about-data-anonymization/\">Article 29 Working Party</a> (today the European Data Protection Board) compared about 7 methods, and Differential Privacy was the only one that could defend against all the re-identification attacks it tested. <a href=\"https://pvml.com/blog/the-european-data-act-a-new-era-for-privacy-regulation/#:~:text=Differential%20privacy%20enables%20secure%20data%20sharing%20while%20providing%20robust%20privacy%20guarantees.&amp;text=Extending%20privacy%20scrutiny%20to%20non,personal%20data%20under%20the%20GDPR.\">The EU Data Act</a> further underscores the importance of privacy-preserving technologies (PPT) like Differential Privacy. Our approach enables clients to securely access sensitive data without ever compromising privacy. As a SOC2-certified company, we adhere to rigorous standards for security, availability, and confidentiality, reaffirming our commitment to protecting data by design.</p>\n<img src=\"https://content.pstmn.io/0e215e0b-7f7a-4d83-bd7a-50ea22947555/UFZNTCAoMSkucG5n\" alt=\"PVML's%20Certifications\" width=\"424\" height=\"140\">\n\n<h2 id=\"pvml-architecture\">PVML Architecture</h2>\n<p>PVML's virtual databases require no installation, data movement, duplication, or modifications. This approach ensures that we do not store any sensitive raw data while providing a quick onboarding process with no changes to the existing workflow of end users.</p>\n<p>PVML can be deployed as a SaaS solution in any cloud environment (AWS, GCP, Azure, Gov cloud) or on-premises in air-gapped environments.</p>\n<img src=\"https://content.pstmn.io/15dd45b4-42ac-4380-8d8a-3b05823470f0/UFZNTCAoMSkucG5n\" alt=\"PVML%20Architecture%20(GCP%20example)\">\n\n<h2 id=\"how-do-i-get-started\">How do I get started?</h2>\n<h3 id=\"via-api\">Via API</h3>\n<p>This documentation thoroughly covers all the essential APIs needed to manage your data access effectively.</p>\n<p>To get your API key and get started, navigate to <strong><code>Connections &gt; REST API &gt; Create</code>****.</strong></p>\n<h3 id=\"authentication\">Authentication</h3>\n<p>Send your API key in the <code>Authorization</code> header of every request:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>Authorization: Bearer &lt;your API key&gt;\n</code></pre><p>In Postman, add a variable <code>token</code> with your API key, in an environment or in the collection's <strong>Variables</strong> tab. All requests use it. Do the same for the IDs in the URLs, for example <code>{{workspace_id}}</code>.</p>\n<p>You can open the documentation on Postman to easily test and run API calls.</p>\n<img src=\"https://content.pstmn.io/987b1caf-62b5-4687-bc4c-d864a2152a32/U2NyZWVuc2hvdCAyMDI1LTAyLTI1IGF0IDE0LjM0LjI1LnBuZw==\" alt=\"Test%20and%20execute%20PVML%20API%20calls\">\n\n<p>The fundamental elements necessary for PVML to function are the <strong>Datasources and Policies</strong>. Once these are configured and users are invited/created, you can use the policies as building blocks to create your desired <strong>Virtual DBs</strong>.</p>\n<p>We'll guide you through each step in the following sections to help you get everything up and running!</p>\n<h3 id=\"via-python-sdk\">Via Python SDK</h3>\n<p>The <a href=\"https://github.com/PVML-ltd/pvml-sdk-python\">PVML Python SDK</a> streamlines access to all key features of our platform, offering pre-built methods for API operations and efficient handling of data access workflows. It’s designed to help you integrate and automate tasks with minimal setup.</p>\n<img src=\"https://content.pstmn.io/27b7f1fa-162d-4527-aaca-4ed550863a3d/U2NyZWVuc2hvdCAyMDI2LTA1LTE4IGF0IDE1LjM2LjI5LnBuZw==\" alt=\"Using%20the%20PVML%20Python%20SDK%20with%20Langchain\">\n\n<h3 id=\"via-user-interface-platform\">Via User Interface (Platform)</h3>\n<p>PVML is fully available through the API, but we also have a really user-friendly UI platform with a complete walkthrough to guide you through everything hand in hand!</p>\n<img src=\"https://content.pstmn.io/05624239-13a6-41d5-b69b-4a7d71e443e8/U2NyZWVuc2hvdCAyMDI2LTA1LTE4IGF0IDE1LjM0LjE3LnBuZw==\" alt=\"PVML%20Platform\">\n\n<h3 id=\"contact-and-support\"><strong>Contact and Support</strong></h3>\n<p>In case you have questions, want to report an error or bug, or simply need additional assistance - don't hesitate to contact us at <a href=\"https://mailto:support@pvml.com\"><b>support@pvml.com</b></a> or fill out a form <a href=\"https://pvml.com/contact-us/\">here</a>.</p>\n</body></html>","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","toc":[{"content":"📄 Get started here!","slug":"get-started-here"}],"owner":"27304672","collectionId":"230ce1f4-4c29-4006-87b5-45620ae91c61","publishedId":"2sBYHXQduf","public":true,"customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"7177F8"},"publishDate":"2026-11-02T22:06:26.000Z"},"item":[{"name":"Guide: Quick start","item":[],"id":"351fedb4-9e17-48b3-9f00-b6d32b5d91f8","description":"<p>This guide takes you from zero to the first secure query in about 15 minutes. Each step names the request in this collection and the place in the platform, so you can use either one.</p>\n<h3 id=\"the-story\">The story</h3>\n<p>Maya runs the data platform at a bank. The branch analysts want a dashboard of accounts per city. They may see the account data, but <strong>not</strong> the names of the account holders. Maya does not want to copy data or change the database.</p>\n<p>With PVML, she connects the database once and creates a <strong>VDB</strong> (virtual database) for the analysts. The analysts connect their BI tool to the VDB, not to the database.</p>\n<p>💡 The tables in this guide are examples. Use your own names (see Step 2).</p>\n<h3 id=\"before-you-start\">Before you start</h3>\n<ol>\n<li><strong>Workspace ID:</strong> in the platform, <strong>Settings &gt; General</strong>. Put it in a Postman variable <code>workspace_id</code>.</li>\n<li><strong>API key:</strong> in the platform, <strong>Connections &gt; REST API &gt; Create New</strong>. Put it in a Postman variable <code>token</code>. Every request sends it as <code>Authorization: Bearer &lt;key&gt;</code>.</li>\n<li><strong>Network:</strong> PVML must be able to reach your database (host and port open for PVML).</li>\n</ol>\n<h3 id=\"step-1-connect-the-database\">Step 1: Connect the database</h3>\n<ol>\n<li>Check the connection details with <strong>Test Datasource Connection</strong>. It returns <code>success</code> or <code>failed</code>.</li>\n<li>Connect it with the <strong>Connect</strong> request of your type, for example <strong>Connect Postgres</strong>. Save the <code>id</code> from the response as <code>datasource_id</code>.</li>\n</ol>\n<p>In the platform: <strong>Datasources</strong>.</p>\n<p>💡 Use a database user with <strong>read-only</strong> rights, unless you plan write policies. PVML can never allow more than this database user can do.</p>\n<h3 id=\"step-2-wait-for-the-snapshot\">Step 2: Wait for the snapshot</h3>\n<p>PVML reads the structure of the database (schemas, tables, columns). This is the <strong>snapshot</strong>. It starts by itself after you connect.</p>\n<ul>\n<li>Check it with <strong>Get Snapshot Status</strong> until <code>lastRefreshStatus</code> is <code>completed</code>.</li>\n<li>PVML does not copy your data. The snapshot is the structure, plus data samples in a <strong>Standard</strong> snapshot.</li>\n<li><strong>Get Snapshot</strong> lists your schemas, tables and columns with their exact names.</li>\n</ul>\n<h3 id=\"step-3-create-the-policies\">Step 3: Create the policies</h3>\n<p>Policies say what the analysts may see. Maya creates two:</p>\n<ol>\n<li><strong>A column policy</strong> (what they may read): the whole <code>\"bank\".\"core\"</code> schema. Use <strong>Create Column Policy</strong> with:</li>\n</ol>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>[{\"schema\": \"\\\"bank\\\".\\\"core\\\"\", \"table\": \"\", \"column\": \"\"}]\n</code></pre><p>An empty <code>table</code> or <code>column</code> means \"all\".</p>\n<ol>\n<li><strong>A masking policy</strong> (what they see hidden): the holder names. Use <strong>Create Masking Policy</strong> with:</li>\n</ol>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>[{\"column\": \"\\\"bank\\\".\\\"core\\\".\\\"accounts\\\".\\\"holder_name\\\"\", \"unmaskLeft\": 0, \"unmaskRight\": 0}]\n</code></pre><p>Save both policy <code>id</code>s.</p>\n<p>In the platform: <strong>Policies</strong>, tabs <strong>Column</strong> and <strong>Masking</strong>.</p>\n<h3 id=\"step-4-create-the-vdb\">Step 4: Create the VDB</h3>\n<p>Use <strong>Create Virtual DB</strong> with:</p>\n<ul>\n<li><code>datasourceId</code>: from Step 1.</li>\n<li><code>permissionsIds</code>: the two policy IDs from Step 3.</li>\n<li><code>entitiesIds</code>: leave it empty for now. You add the analysts in Step 6.</li>\n</ul>\n<p>Save the VDB <code>id</code> as <code>vdb_id</code>.</p>\n<p>In the platform: <strong>Virtual DBs</strong>.</p>\n<h3 id=\"step-5-test-it\">Step 5: Test it</h3>\n<p>Run a query as yourself with <strong>Execute Query on Virtual DB</strong>:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>SELECT a.holder_name, b.city\nFROM \"bank\".\"core\".\"accounts\" a\nJOIN \"bank\".\"core\".\"branches\" b ON a.branch_id = b.branch_id\nLIMIT 5\n</code></pre><p>The result has real cities and masked names (<code>*****</code>). The data in the database did not change.</p>\n<h3 id=\"step-6-give-the-analysts-access\">Step 6: Give the analysts access</h3>\n<ul>\n<li><strong>People:</strong> invite them with <strong>Invite Users</strong> (role <code>member</code>), or add them with SSO (see <strong>Workspaces &gt; Guide: SSO and SCIM setup</strong>).</li>\n<li><strong>Groups are easier:</strong> create a group \"branch analysts\" (<strong>Create Group</strong>), add the people, and give the group access with <strong>Update Virtual DB Users</strong>. New analysts only need to join the group.</li>\n</ul>\n<h3 id=\"step-7-connect-the-bi-tool\">Step 7: Connect the BI tool</h3>\n<ol>\n<li>Create a service account for the dashboard (<strong>Create Service Account</strong>) and add it to the VDB (<strong>Update Virtual DB Users</strong>).</li>\n<li>Create a connection string for it (<strong>Create ODBC/JDBC Connection String</strong>).</li>\n<li>Put the fields from the response in the BI tool. See the <strong>Connections</strong> folder.</li>\n</ol>\n<p>💡 An analyst who uses an AI client instead connects it to the <strong>MCP server</strong> of the VDB. See <strong>GenAI &gt; MCP</strong>.</p>\n<h3 id=\"step-8-check-the-audit\">Step 8: Check the audit</h3>\n<p>Every query on the VDB is in the <strong>Data Access Audit</strong> (<strong>Get Audit Records</strong>): who ran it, from which tool, how long it took, and if it succeeded. Every change Maya made (policies, VDB, users) is in the <strong>System Audit</strong> (<strong>Get Activity Log</strong>).</p>\n<h3 id=\"what-next\">What next?</h3>\n<ul>\n<li>Show each analyst only the rows of their own branch: <strong>Policies &gt; Guide: Row-level security (RLS)</strong>.</li>\n<li>Add more policies and learn how they combine: <strong>Policies &gt; Guide: How policies work together</strong>.</li>\n<li>Pick the right masking method: <strong>Policies &gt; Guide: Masking strategies</strong>.</li>\n</ul>\n","_postman_id":"351fedb4-9e17-48b3-9f00-b6d32b5d91f8","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Workspaces","item":[{"name":"Workspace CRUD","item":[{"name":"Create Workspace","id":"4be938c5-cee7-4a7d-b66a-0f4b0e9a87e6","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"name\": \"my workspace\",\n    \"description\": \"my team's workspace\"}"},{"key":"image","type":"text","value":""}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces","description":"<p>This endpoint allows you to create a new workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<p>This request uses no parameters.</p>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>Form data containing:</p>\n<ul>\n<li><p><code>data</code> (text): Json with the following properties:</p>\n<ul>\n<li><p><code>name</code>(string): The name of the new workspace.</p>\n</li>\n<li><p><code>description</code>(string): The description of the new workspace.</p>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p>The response is a <code>workspace</code> object in json format that includes the details of the newly created workspace:</p>\n<ul>\n<li><p><code>id</code> (string): The unique identifier of the new workspace.</p>\n</li>\n<li><p><code>name</code> (string): The name of the new workspace.</p>\n</li>\n<li><p><code>description</code> (string): The description of the new workspace.</p>\n</li>\n<li><p><code>image</code> (string): The path to the workspace's image.</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"7fc70df4-87f2-4259-a8d8-084952ba905b","name":"Create Workspace - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"name\": \"my workspace\",\n    \"description\": \"my team's workspace\"}"},{"key":"image","type":"text","value":""}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"workspace\": {\n        \"id\": \"a1997551-93b5-5adc-9403-748712e22687\",\n        \"name\": \"my workspace\",\n        \"description\": \"my team's workspace\",\n        \"image\": \"\"\n    }\n}"}],"_postman_id":"4be938c5-cee7-4a7d-b66a-0f4b0e9a87e6"},{"name":"Get Workspace","id":"84335a01-2785-49f5-97fd-646a88b8f597","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}?enrich=false","description":"<p>This endpoint retrieves a specific workspace by id.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><code>workspace_id</code> (string): The unique identifier of the workspace.</li>\n<li><code>enrich</code> (boolean, query parameter): When <code>true</code>, the response also has the workspace image.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The workspace, with these fields:</p>\n<ul>\n<li><p><code>id</code> (string): The unique identifier for the workspace.</p>\n</li>\n<li><p><code>name</code> (string): The name of the workspace.</p>\n</li>\n<li><p><code>description</code> (string): A brief description of the workspace.</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}"],"host":["platform","pvml","com"],"query":[{"key":"enrich","value":"false"}],"variable":[]}},"response":[{"id":"0903b0a5-9e26-4186-ba04-311ac384531f","name":"Get Workspace - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}?enrich=false","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}"],"query":[{"key":"enrich","value":"false"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"a1997551-93b5-5adc-9403-748712e22687\",\n    \"name\": \"new name\",\n    \"description\": \"my team's workspace\"\n}"}],"_postman_id":"84335a01-2785-49f5-97fd-646a88b8f597"},{"name":"Get Workspaces","id":"d8537bd2-43b6-4aab-bef0-b42c5715d4de","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/users/{{user_id}}/workspaces?enrich=false&summary=true","description":"<p>This endpoint retrieves the workspaces associated with a specific user.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>user_id</code></strong>(string): the unique id of the user in the workspace</p>\n</li>\n<li><p><strong><code>summary</code></strong> (boolean, query parameter): Whether to return more details (number of users and image).</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response will contain <strong><code>workspaces</code></strong> array in JSON format, where each workspace object includes the following fields:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): The unique identifier for the workspace.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): The name of the workspace.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A brief description of the workspace.</p>\n</li>\n<li><p><strong><code>numOfUsers</code></strong> (number): The number of users associated with the workspace (returned if <code>summary</code> is <code>true</code>).</p>\n</li>\n<li><p><strong><code>image</code></strong> (string): The image associated with the workspace (returned if <code>summary</code> is <code>true</code>).</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","users","{{user_id}}","workspaces"],"host":["platform","pvml","com"],"query":[{"key":"enrich","value":"false"},{"key":"summary","value":"true"}],"variable":[]}},"response":[{"id":"242943e4-8514-4dab-8af9-ae25b99948c3","name":"Get Workspaces without Summary - 200 Success","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/users/{{user_id}}/workspaces?summary=false","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","users","{{user_id}}","workspaces"],"query":[{"key":"summary","value":"false"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"workspaces\": [\n        {\n            \"id\": \"6717b037-a60f-5e35-9ba8-a7da496df7eb\",\n            \"name\": \"my workspace\",\n            \"description\": \"my team's workspace\",\n            \"numOfUsers\": 4,\n            \"image\": \"\"\n        },\n        {\n            \"id\": \"35b17418-e6d8-50fb-b361-02dbb98d499e\",\n            \"name\": \"analytics\",\n            \"description\": \"analytics team\",\n            \"numOfUsers\": 12,\n            \"image\": \"\"\n        }\n    ]\n}"},{"id":"94cd3d69-76b0-483a-b76d-6fbf19c57ff1","name":"Get Workspaces with Summary - 200 Success","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/users/{{user_id}}/workspaces?summary=true","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","users","{{user_id}}","workspaces"],"query":[{"key":"summary","value":"true"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"workspaces\": [\n        {\n            \"id\": \"1cebc4bf-9ff1-5455-93fa-f02e1d5c1d36\",\n            \"name\": \"my workspace\",\n            \"description\": \"my team's workspace\",\n            \"numOfUsers\": 4,\n            \"image\": \"\"\n        },\n        {\n            \"id\": \"8adb9deb-1c14-5b0b-a1bc-ea7b877966bd\",\n            \"name\": \"analytics\",\n            \"description\": \"analytics team\",\n            \"numOfUsers\": 12,\n            \"image\": \"\"\n        },\n        {\n            \"id\": \"dbdfa7dd-0948-559e-a00a-cf7a5690cc24\",\n            \"name\": \"my workspace\",\n            \"description\": \"my team's workspace\",\n            \"numOfUsers\": 4,\n            \"image\": \"\"\n        }\n    ]\n}"}],"_postman_id":"d8537bd2-43b6-4aab-bef0-b42c5715d4de"},{"name":"Update Workspace","id":"3250bf91-3d8e-45a3-bf46-f66c9968fe91","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"name\": \"new name\",\n    \"description\": \"my team's workspace\"\n}"},{"key":"image","type":"file","value":null,"disabled":true}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}","description":"<p>This endpoint allows you to update a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><code>workspace_id</code>(string): the unique id of the workspace.</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>Form data containing the fields to update:</p>\n<ul>\n<li><p><code>data</code> (text): Json with the following properties:</p>\n<ul>\n<li><p><code>name</code>(string): The name of the workspace.</p>\n</li>\n<li><p><code>description</code>(string): The description of the workspace.</p>\n</li>\n</ul>\n</li>\n<li><p><code>image</code> (File): The image for the workspace.</p>\n</li>\n</ul>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p>Upon successful update, the server returns a status code of 201 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"845153fa-e220-4eac-a4af-ab62d912396c","name":"201 Created","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"formdata","formdata":[{"key":"image","type":"file","src":[]}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"\"\""}],"_postman_id":"3250bf91-3d8e-45a3-bf46-f66c9968fe91"},{"name":"Delete Workspace","id":"413ec7dc-e4e9-4c54-affa-d94209478908","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}","description":"<p>This endpoint is used to delete a specific workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><code>workspace_id</code> (string): The unique identifier of the workspace to be deleted.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Upon successful deletion of the workspace, the server returns a status code of 200 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"7d94f4ab-ecf5-478e-a503-ad93d5de739e","name":"Delete Workspace - 200 Success","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"Text","header":[],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"413ec7dc-e4e9-4c54-affa-d94209478908"}],"id":"29644d0d-4a37-45d5-ae7b-45fce78e6395","_postman_id":"29644d0d-4a37-45d5-ae7b-45fce78e6395","description":"","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Guide: SSO and SCIM setup","item":[],"id":"219a3930-b659-4902-963c-86656464288a","description":"<p>This guide connects PVML to your identity provider (IdP), such as Okta, Microsoft Entra ID or Google. After it:</p>\n<ul>\n<li>people <strong>log in</strong> to PVML with their company account (<strong>SSO</strong>), and</li>\n<li>users, groups and user attributes <strong>stay in sync</strong> with the IdP by themselves (<strong>SCIM</strong>).</li>\n</ul>\n<h3 id=\"the-story\">The story</h3>\n<p>A bank has 300 analysts in Okta. Each one has a <code>department</code> in their Okta profile. The security team wants three things:</p>\n<ol>\n<li>No separate PVML passwords.</li>\n<li>When someone joins or leaves in Okta, PVML follows the same day, with no manual work.</li>\n<li>Each analyst sees only the rows of their own department, based on the value in Okta.</li>\n</ol>\n<p>SSO solves 1. SCIM solves 2. SCIM plus a linked attribute catalog solves 3.</p>\n<h3 id=\"part-1-sso-log-in-with-the-company-account\">Part 1: SSO (log in with the company account)</h3>\n<ol>\n<li><strong>Create the provider in PVML.</strong> In the platform: <strong>Settings &gt; SSO</strong>. With the API: <strong>Create SSO Integration</strong>, with:<ul>\n<li><code>type</code>: <code>saml</code> or <code>oauth</code>.</li>\n<li><code>vendor</code>: <code>okta</code>, <code>google</code>, <code>microsoft</code> or <code>generic</code>.</li>\n<li>For SAML: the <strong>Metadata URL</strong> of the SAML app in your IdP.</li>\n<li>For OAuth: the <strong>Client ID</strong>, <strong>Client Secret</strong>, <strong>Auth URL</strong>, <strong>Token URL</strong> and <strong>User Info URL</strong> of the app in your IdP.</li>\n</ul>\n</li>\n<li><strong>Copy the PVML values into your IdP app.</strong> The platform shows them after you create the provider:<ul>\n<li>OAuth: the <strong>Redirect URL</strong> (API: <strong>Get Redirect URL</strong>).</li>\n<li>SAML: the <strong>ACS URL</strong> and the <strong>Entity Id</strong>.</li>\n</ul>\n</li>\n<li><strong>Share the login URL.</strong> The response has a <strong><code>workspaceLoginUrl</code></strong> (platform: <strong>Workspace Login URL</strong>). Users of the workspace log in with it.</li>\n</ol>\n<p>Save the provider <code>id</code>. SCIM needs it.</p>\n<h3 id=\"part-2-scim-keep-users-and-groups-in-sync\">Part 2: SCIM (keep users and groups in sync)</h3>\n<p><strong>In the platform (easiest):</strong> open <strong>Settings &gt; SSO</strong>, open the provider, turn on <strong>Enable SCIM provisioning</strong> and click <strong>Generate SCIM credentials</strong>. Copy the <strong>Base URL</strong> and the <strong>Auth token</strong> into the provisioning settings of your IdP.</p>\n<p>⚠️ The token is shown only once. If you generate new credentials, the old token stops working after you update your IdP.</p>\n<p><strong>With the API:</strong> see the steps in the <strong>SCIM</strong> folder.</p>\n<p>After this, your IdP manages PVML users:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>In your IdP</th>\n<th>In PVML</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Assign a person to the PVML app</td>\n<td>A user is created</td>\n</tr>\n<tr>\n<td>Change their profile</td>\n<td>The user is updated, with the new attribute values</td>\n</tr>\n<tr>\n<td>Unassign them, or they leave</td>\n<td>The user is deactivated or removed</td>\n</tr>\n<tr>\n<td>Push a group</td>\n<td>A PVML group is created, with the same members</td>\n</tr>\n<tr>\n<td>Give the <code>admin</code> role</td>\n<td>The user becomes a workspace admin</td>\n</tr>\n</tbody>\n</table>\n</div><p>💡 <strong>Tip:</strong> give VDB access to <strong>groups</strong>, not to single people. Then a new analyst who joins the right group in Okta gets the right VDBs in PVML, with no work in PVML.</p>\n<h3 id=\"part-3-attributes-from-the-idp-for-row-level-security\">Part 3: Attributes from the IdP (for row-level security)</h3>\n<p>SCIM also sends the profile fields of each user, for example <code>department</code> or <code>country</code>. To use one in a policy, link an <strong>attribute catalog</strong> to the SSO provider:</p>\n<ol>\n<li><strong>Create Attribute Catalog</strong> with:<ul>\n<li><code>name</code>: for example <code>idp</code>.</li>\n<li><code>ssoProviderId</code>: the provider <code>id</code> from Part 1.</li>\n<li><code>urn</code>: the SCIM schema of the field. For the standard enterprise fields (such as <code>department</code>): <code>urn:ietf:params:scim:schemas:extension:enterprise:2.0:User</code>.</li>\n</ul>\n</li>\n<li><strong>Define Attributes</strong> in this catalog with the <strong>same name</strong> as the SCIM field, for example <code>department</code> (type <code>text</code>).</li>\n<li>That's it. When the IdP sends or updates a user, PVML fills the value. Check it with <strong>Get User Attributes</strong>.</li>\n<li>Use it in a row policy, for example:</li>\n</ol>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>\"finance\".\"core\".\"expenses\".\"department\" = {{idp.department}}\n</code></pre><p>Dana has <code>department = Finance</code> in Okta, so she sees only the expenses of Finance. If HR moves her to Sales in Okta, she sees the expenses of Sales after the next sync, with no change in PVML.</p>\n<p>💡 Values that come from the IdP are managed by the IdP. You can not delete them in PVML. Change them in the IdP.</p>\n<p>⚠️ If you delete the SSO provider, its linked catalogs and their values are removed too. Policies that use them then block the queries (a missing attribute never gives more access).</p>\n<h3 id=\"checklist\">Checklist</h3>\n<ul>\n<li> SSO provider created, IdP app configured, login URL shared.</li>\n<li> SCIM credentials generated and set in the IdP.</li>\n<li> Groups pushed from the IdP and attached to VDBs.</li>\n<li> Attribute catalog linked to the provider, attributes defined with the SCIM field names.</li>\n<li> Row policies use <code>{{catalog.attribute}}</code>. See <strong>Policies &gt; Guide: Row-level security (RLS)</strong>.</li>\n</ul>\n","_postman_id":"219a3930-b659-4902-963c-86656464288a","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"SSO","item":[{"name":"Create SSO Integration","id":"ea5cb4e6-d3e6-4061-a522-6dee88893738","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"createSsoProviderData\": {\n        \"type\": \"saml\",\n        \"vendor\": \"okta\",\n        \"name\": \"my_sso\",\n        \"userAttributesFetch\": \"none\",\n        \"configData\": {\n            \"samlData\": {\n                \"metadataUrl\": \"https://mydomain.okta.com/app/abc123/sso/saml/metadata\"\n            }\n        }\n    }\n}"}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso","description":"<p>Connects an SSO provider (identity provider) to the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>Form data with one field, <strong><code>data</code></strong> (text), a JSON with <strong><code>createSsoProviderData</code></strong>:</p>\n<ul>\n<li><strong><code>type</code></strong> (string): <code>saml</code> or <code>oauth</code>.</li>\n<li><strong><code>vendor</code></strong> (string): <code>okta</code>, <code>google</code>, <code>microsoft</code> or <code>generic</code>.</li>\n<li><strong><code>name</code></strong> (string, required): A name for the provider.</li>\n<li><strong><code>configData</code></strong> (object): Provider settings:<ul>\n<li>For SAML: <code>samlData.metadataUrl</code> (the metadata URL of your SAML app).</li>\n<li>For OAuth: <code>oauthData</code> with <code>clientId</code>, <code>clientSecret</code>, <code>authUrl</code>, <code>tokenUrl</code>, <code>userInfoUrl</code>, <code>scopes</code>.</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The provider: <code>id</code>, <code>workspaceId</code>, <code>vendor</code>, <code>type</code>, <code>name</code>, <code>createdAt</code>, <code>configData</code>, and <strong><code>workspaceLoginUrl</code></strong>. Users of the workspace log in with this URL.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","sso"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"e2092928-04b3-4f7e-81ab-08a431a60a6a","name":"Create SSO Integration - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"createSsoProviderData\": {\n        \"type\": \"saml\",\n        \"vendor\": \"okta\",\n        \"name\": \"my_sso\",\n        \"userAttributesFetch\": \"none\",\n        \"configData\": {\n            \"samlData\": {\n                \"metadataUrl\": \"https://mydomain.okta.com/app/abc123/sso/saml/metadata\"\n            }\n        }\n    }\n}"}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"f75163b2-0ee9-55d5-acb6-9cfe445ec361\",\n    \"workspaceId\": \"a1997551-93b5-5adc-9403-748712e22687\",\n    \"vendor\": \"okta\",\n    \"type\": \"saml\",\n    \"userAttributesFetch\": \"none\",\n    \"createdAt\": \"2026-10-29T23:56:10.539682Z\",\n    \"name\": \"my_sso\",\n    \"workspaceLoginUrl\": \"https://platform.pvml.com/login/a1997551-93b5-5adc-9403-748712e22687\",\n    \"configData\": {\n        \"samlData\": {\n            \"metadataUrl\": \"https://mydomain.okta.com/app/abc123/sso/saml/metadata\"\n        }\n    }\n}"}],"_postman_id":"ea5cb4e6-d3e6-4061-a522-6dee88893738"},{"name":"Get SSO Providers","id":"ba373c79-c385-4b58-a9b0-e5cd72377c4f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","sso"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"74229e58-6cdd-4cfc-89ad-7660302fd67f","name":"Get SSO Providers - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"ssoProviders\": [\n        {\n            \"id\": \"f75163b2-0ee9-55d5-acb6-9cfe445ec361\",\n            \"workspaceId\": \"a1997551-93b5-5adc-9403-748712e22687\",\n            \"type\": \"saml\",\n            \"userAttributesFetch\": \"none\",\n            \"configData\": {},\n            \"vendor\": \"okta\",\n            \"name\": \"my_sso\",\n            \"createdAt\": \"0001-01-01T00:00:00Z\",\n            \"workspaceLoginUrl\": \"https://platform.pvml.com/login/a1997551-93b5-5adc-9403-748712e22687\"\n        }\n    ]\n}"}],"_postman_id":"ba373c79-c385-4b58-a9b0-e5cd72377c4f"},{"name":"Get Redirect URL","id":"7756033d-6996-4e44-a4ec-854afcd6f3c1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso/redirectUrl","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","sso","redirectUrl"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"eccd2c73-06bb-419d-93bf-cfe4de50905d","name":"Get Redirect URL - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso/redirectUrl"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"redirectUrl\": \"https://platform.pvml.com/platform/api/v1/auth/callback\"\n}"}],"_postman_id":"7756033d-6996-4e44-a4ec-854afcd6f3c1"},{"name":"Get SSO Integration","id":"d1e77d25-f4b6-4ee4-81fa-0ce7ca2cadc8","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso/{{sso_integration_id}}","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","sso","{{sso_integration_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"bcec49b9-2324-4dff-a53c-19b731eb9fc5","name":"Get SSO Integration - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso/{{sso_integration_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"ssoProvider\": {\n        \"id\": \"f75163b2-0ee9-55d5-acb6-9cfe445ec361\",\n        \"workspaceId\": \"a1997551-93b5-5adc-9403-748712e22687\",\n        \"type\": \"saml\",\n        \"userAttributesFetch\": \"none\",\n        \"configData\": {\n            \"samlData\": {\n                \"metadataUrl\": \"https://mydomain.okta.com/app/abc123/sso/saml/metadata\",\n                \"acsUrl\": \"https://platform.pvml.com/platform/api/v1/auth/saml/acs/f75163b2-0ee9-55d5-acb6-9cfe445ec361\",\n                \"entityId\": \"PVML\"\n            }\n        },\n        \"vendor\": \"okta\",\n        \"name\": \"my_sso\",\n        \"createdAt\": \"2026-10-29T23:56:10.539682Z\",\n        \"workspaceLoginUrl\": \"https://platform.pvml.com/login/a1997551-93b5-5adc-9403-748712e22687\"\n    }\n}"}],"_postman_id":"d1e77d25-f4b6-4ee4-81fa-0ce7ca2cadc8"},{"name":"Update SSO Provider","id":"cad0b0fb-e61c-4f9a-84ee-5f5ae3c2de59","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"updateSsoProviderData\": {\n        \"vendor\": \"okta\",\n        \"type\": \"saml\",\n        \"name\": \"my_sso\",\n        \"userAttributesFetch\": \"none\",\n        \"configData\": {\n            \"samlData\": {\n                \"metadataUrl\": \"https://mydomain.okta.com/app/abc123/sso/saml/metadata\"\n            }\n        }\n    }\n}"}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso/{{sso_integration_id}}","description":"<p>Updates an SSO provider.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>Form data with one field, <strong><code>data</code></strong> (text), a JSON with <strong><code>updateSsoProviderData</code></strong>. It has the same fields as in <strong>Create SSO Integration</strong>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The updated provider.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","sso","{{sso_integration_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[],"_postman_id":"cad0b0fb-e61c-4f9a-84ee-5f5ae3c2de59"},{"name":"Delete SSO Integration","id":"dafec68d-e820-4f2e-a76e-91d781a3a104","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso/{{sso_integration_id}}","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","sso","{{sso_integration_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"9904ea6e-a2fc-4bfe-ae89-8b23097ca029","name":"Delete SSO Integration - 200 Success","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/sso/{{sso_integration_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"Text","header":[],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"dafec68d-e820-4f2e-a76e-91d781a3a104"}],"id":"21cbbb80-6e85-47f1-93ad-7dbb29eeb93b","description":"<p>PVML integrates with your IAM to streamline user management and leverage user attributes in dynamic policies (e.g., creating row-level security based on a user’s region in Okta).</p>\n<p>📘 See <strong>Guide: SSO and SCIM setup</strong> for the full setup, with SCIM and attributes from your identity provider.</p>\n","_postman_id":"21cbbb80-6e85-47f1-93ad-7dbb29eeb93b","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"SCIM","item":[{"name":"SCIM Get Service Provider Config","id":"ccc8bde1-f1be-40c3-a153-58c43239e8f3","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/ServiceProviderConfig","description":"<p>Returns the SCIM features of PVML. IdPs read it when you set up provisioning.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","ServiceProviderConfig"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"0cc3f0dd-e289-4133-b4c6-3a7220aadc49","name":"SCIM Get Service Provider Config - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/ServiceProviderConfig"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig\"\n    ],\n    \"patch\": {\n        \"supported\": true\n    },\n    \"bulk\": {\n        \"supported\": false\n    },\n    \"filter\": {\n        \"supported\": false\n    },\n    \"changePassword\": {\n        \"supported\": false\n    },\n    \"sort\": {\n        \"supported\": false\n    },\n    \"etag\": {\n        \"supported\": false\n    }\n}"}],"_postman_id":"ccc8bde1-f1be-40c3-a153-58c43239e8f3"},{"name":"SCIM Create User","id":"5d87bd74-82bc-45fc-a22e-bd1a3955d6c6","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:User\"\n    ],\n    \"userName\": \"dana.levi@example.com\",\n    \"externalId\": \"00u1a2b3c4\",\n    \"name\": {\n        \"givenName\": \"Dana\",\n        \"familyName\": \"Levi\"\n    },\n    \"emails\": [\n        {\n            \"value\": \"dana.levi@example.com\",\n            \"primary\": true\n        }\n    ],\n    \"active\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users","description":"<p>Creates a PVML user in the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>A SCIM User: <code>userName</code> (the email, required), <code>externalId</code> (the ID in your IdP), <code>name</code>, <code>emails</code>, <code>active</code>, and optional <code>roles</code>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 201 with the user and its PVML <code>id</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Users"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"1255fe2c-c21b-4f7d-8b7a-429367b22245","name":"SCIM Create User - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:User\"\n    ],\n    \"userName\": \"dana.levi@example.com\",\n    \"externalId\": \"00u1a2b3c4\",\n    \"name\": {\n        \"givenName\": \"Dana\",\n        \"familyName\": \"Levi\"\n    },\n    \"emails\": [\n        {\n            \"value\": \"dana.levi@example.com\",\n            \"primary\": true\n        }\n    ],\n    \"active\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"active\": true,\n    \"emails\": [\n        {\n            \"primary\": true,\n            \"value\": \"dana.levi@example.com\"\n        }\n    ],\n    \"externalId\": \"00u1a2b3c4\",\n    \"id\": \"50e32e80-0397-5f0a-b129-b2bf880ecd96\",\n    \"name\": {\n        \"familyName\": \"Levi\",\n        \"givenName\": \"Dana\"\n    },\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:User\"\n    ],\n    \"userName\": \"dana.levi@example.com\"\n}"}],"_postman_id":"5d87bd74-82bc-45fc-a22e-bd1a3955d6c6"},{"name":"SCIM Get Users","id":"baf59eea-d05f-498a-90b5-b6d8c4c47c10","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users?filter=userName eq \"dana.levi@example.com\"&startIndex=1&count=100","description":"<p>Lists users, with an optional filter.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A SCIM ListResponse: <code>totalResults</code>, <code>startIndex</code>, <code>itemsPerPage</code> and <code>Resources</code> (the users).</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Users"],"host":["platform","pvml","com"],"query":[{"key":"filter","value":"userName eq \"dana.levi@example.com\""},{"key":"startIndex","value":"1"},{"key":"count","value":"100"}],"variable":[]}},"response":[{"id":"2c8998c6-1a82-48cf-a4ec-ade984e4feb8","name":"SCIM Get Users - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users?filter=userName eq \"dana.levi@example.com\"&startIndex=1&count=100","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Users"],"query":[{"key":"filter","value":"userName eq \"dana.levi@example.com\""},{"key":"startIndex","value":"1"},{"key":"count","value":"100"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"startIndex\": 1,\n    \"itemsPerPage\": 100,\n    \"totalResults\": 1,\n    \"schemas\": [\n        \"urn:ietf:params:scim:api:messages:2.0:ListResponse\"\n    ],\n    \"Resources\": [\n        {\n            \"active\": true,\n            \"emails\": [\n                {\n                    \"primary\": true,\n                    \"value\": \"dana.levi@example.com\"\n                }\n            ],\n            \"externalId\": \"00u1a2b3c4\",\n            \"id\": \"4b4fbec6-f03b-5fe5-8fee-062c74883c5e\",\n            \"name\": {\n                \"familyName\": \"Levi\",\n                \"givenName\": \"Dana\"\n            },\n            \"schemas\": [\n                \"urn:ietf:params:scim:schemas:core:2.0:User\"\n            ],\n            \"userName\": \"dana.levi@example.com\"\n        }\n    ]\n}"}],"_postman_id":"baf59eea-d05f-498a-90b5-b6d8c4c47c10"},{"name":"SCIM Get User","id":"1df9479a-3f87-4a36-a3c6-a767d631c809","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users/{{scim_user_id}}","description":"<p>Returns one user.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n<li><strong><code>scim_user_id</code></strong> (string): The user <code>id</code>.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Users","{{scim_user_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"d8cff5de-0c0d-41a2-a58a-a5c7d4ec089e","name":"SCIM Get User - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users/{{scim_user_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"active\": false,\n    \"emails\": [\n        {\n            \"primary\": true,\n            \"value\": \"dana.levi@example.com\"\n        }\n    ],\n    \"externalId\": \"00u1a2b3c4\",\n    \"id\": \"50e32e80-0397-5f0a-b129-b2bf880ecd96\",\n    \"name\": {\n        \"familyName\": \"Levi-Cohen\",\n        \"givenName\": \"Dana\"\n    },\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:User\"\n    ],\n    \"userName\": \"dana.levi@example.com\"\n}"}],"_postman_id":"1df9479a-3f87-4a36-a3c6-a767d631c809"},{"name":"SCIM Replace User","id":"617d0b3d-f49a-48dc-a0ad-8ea8f3d2a517","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:User\"\n    ],\n    \"userName\": \"dana.levi@example.com\",\n    \"externalId\": \"00u1a2b3c4\",\n    \"name\": {\n        \"givenName\": \"Dana\",\n        \"familyName\": \"Levi-Cohen\"\n    },\n    \"emails\": [\n        {\n            \"value\": \"dana.levi@example.com\",\n            \"primary\": true\n        }\n    ],\n    \"active\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users/{{scim_user_id}}","description":"<p>Replaces all fields of a user.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n<li><strong><code>scim_user_id</code></strong> (string): The user <code>id</code>.</li>\n</ul>\n<p>⚠️ The user must be active.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Users","{{scim_user_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"87ebcc61-ebf8-4c7a-99ca-47d38fcd13e8","name":"SCIM Replace User - 200 OK","originalRequest":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:User\"\n    ],\n    \"userName\": \"dana.levi@example.com\",\n    \"externalId\": \"00u1a2b3c4\",\n    \"name\": {\n        \"givenName\": \"Dana\",\n        \"familyName\": \"Levi-Cohen\"\n    },\n    \"emails\": [\n        {\n            \"value\": \"dana.levi@example.com\",\n            \"primary\": true\n        }\n    ],\n    \"active\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users/{{scim_user_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"active\": true,\n    \"emails\": [\n        {\n            \"primary\": true,\n            \"value\": \"dana.levi@example.com\"\n        }\n    ],\n    \"externalId\": \"00u1a2b3c4\",\n    \"id\": \"efa23475-dec8-5775-b829-6890c0906578\",\n    \"name\": {\n        \"familyName\": \"Levi-Cohen\",\n        \"givenName\": \"Dana\"\n    },\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:User\"\n    ],\n    \"userName\": \"dana.levi@example.com\"\n}"}],"_postman_id":"617d0b3d-f49a-48dc-a0ad-8ea8f3d2a517"},{"name":"SCIM Update User","id":"e377420d-b2ef-4841-bd4c-7ac373f0361b","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:api:messages:2.0:PatchOp\"\n    ],\n    \"Operations\": [\n        {\n            \"op\": \"replace\",\n            \"path\": \"active\",\n            \"value\": false\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users/{{scim_user_id}}","description":"<p>Changes some fields of a user. The most common use: deactivate a user who left.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n<li><strong><code>scim_user_id</code></strong> (string): The user <code>id</code>.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>A SCIM PatchOp. Each operation has <code>op</code> (<code>add</code>, <code>replace</code> or <code>remove</code>), <code>path</code> and <code>value</code>. Paths with filters (<code>[...]</code>) are not supported for users.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The updated user.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Users","{{scim_user_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"438862ef-f954-411f-baa5-dcadbdcf3477","name":"SCIM Update User - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:api:messages:2.0:PatchOp\"\n    ],\n    \"Operations\": [\n        {\n            \"op\": \"replace\",\n            \"path\": \"active\",\n            \"value\": false\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users/{{scim_user_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"active\": false,\n    \"emails\": [\n        {\n            \"primary\": true,\n            \"value\": \"dana.levi@example.com\"\n        }\n    ],\n    \"externalId\": \"00u1a2b3c4\",\n    \"id\": \"efa23475-dec8-5775-b829-6890c0906578\",\n    \"name\": {\n        \"familyName\": \"Levi\",\n        \"givenName\": \"Dana\"\n    },\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:User\"\n    ],\n    \"userName\": \"dana.levi@example.com\"\n}"}],"_postman_id":"e377420d-b2ef-4841-bd4c-7ac373f0361b"},{"name":"SCIM Delete User","id":"b19dd5ca-51a9-4125-88f9-cbb5ff223686","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users/{{scim_user_id}}","description":"<p>Deletes the user from PVML.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n<li><strong><code>scim_user_id</code></strong> (string): The user <code>id</code>.</li>\n</ul>\n<p>💡 To keep the user's settings, deactivate instead (<strong>SCIM Update User</strong>, <code>active: false</code>).</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Users","{{scim_user_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"e17a8570-957b-4f77-be65-cec118156a70","name":"SCIM Delete User - 200 OK","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Users/{{scim_user_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"status\": \"deleted\"\n}"}],"_postman_id":"b19dd5ca-51a9-4125-88f9-cbb5ff223686"},{"name":"SCIM Create Group","id":"45c974ec-948b-4233-a0fb-748c21c93ff7","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:Group\"\n    ],\n    \"displayName\": \"emea analysts\",\n    \"members\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups","description":"<p>Creates a PVML group.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p><code>displayName</code> (required) and <code>members</code> (<code>[{\"value\": \"&lt;user id&gt;\"}]</code>).</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 201 with the group and its <code>id</code>.</p>\n<p>💡 Add members with <strong>SCIM Update Group</strong> after you create the group.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Groups"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"bbb6f6ec-a973-4739-8d7d-39f9fd4ce3df","name":"SCIM Create Group - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:Group\"\n    ],\n    \"displayName\": \"emea analysts\",\n    \"members\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"8eac2e35-1066-5c68-a3f6-eebe796e5077\",\n    \"displayName\": \"emea analysts\",\n    \"members\": [],\n    \"meta\": {\n        \"resourceType\": \"Group\",\n        \"created\": \"2026-10-30T01:08:07.257351Z\",\n        \"lastModified\": \"2026-10-30T01:08:07.257351Z\"\n    },\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:Group\"\n    ]\n}"}],"_postman_id":"45c974ec-948b-4233-a0fb-748c21c93ff7"},{"name":"SCIM Get Groups","id":"d519740c-084c-4846-b561-b09ac75196e3","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups?filter=displayName eq \"emea analysts\"","description":"<p>Lists groups, with an optional filter.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Groups"],"host":["platform","pvml","com"],"query":[{"key":"filter","value":"displayName eq \"emea analysts\""}],"variable":[]}},"response":[{"id":"fa42bace-62de-4454-8680-97c8d9f0b4b4","name":"SCIM Get Groups - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups?filter=displayName eq \"emea analysts\"","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Groups"],"query":[{"key":"filter","value":"displayName eq \"emea analysts\""}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"startIndex\": 1,\n    \"itemsPerPage\": 100,\n    \"totalResults\": 1,\n    \"schemas\": [\n        \"urn:ietf:params:scim:api:messages:2.0:ListResponse\"\n    ],\n    \"Resources\": [\n        {\n            \"id\": \"8eac2e35-1066-5c68-a3f6-eebe796e5077\",\n            \"displayName\": \"emea analysts\",\n            \"members\": null,\n            \"meta\": {\n                \"resourceType\": \"Group\",\n                \"created\": \"2026-10-30T01:08:07.257351Z\",\n                \"lastModified\": \"2026-10-30T01:08:07.438465Z\"\n            },\n            \"schemas\": [\n                \"urn:ietf:params:scim:schemas:core:2.0:Group\"\n            ]\n        }\n    ]\n}"}],"_postman_id":"d519740c-084c-4846-b561-b09ac75196e3"},{"name":"SCIM Get Group","id":"844970ba-cb9a-4b70-bf74-34a37749e54e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups/{{scim_group_id}}","description":"<p>Returns one group with its members.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n<li><strong><code>scim_group_id</code></strong> (string): The group <code>id</code>.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Groups","{{scim_group_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"f4f6a66a-00cd-42b6-837e-358daba4a065","name":"SCIM Get Group - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups/{{scim_group_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"8eac2e35-1066-5c68-a3f6-eebe796e5077\",\n    \"displayName\": \"emea analysts\",\n    \"members\": [\n        {\n            \"value\": \"50e32e80-0397-5f0a-b129-b2bf880ecd96\",\n            \"$ref\": \"\",\n            \"display\": \"dana.levi@example.com\"\n        }\n    ],\n    \"meta\": {\n        \"resourceType\": \"Group\",\n        \"created\": \"2026-10-30T01:08:07.257351Z\",\n        \"lastModified\": \"2026-10-30T01:08:07.438465Z\"\n    },\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:Group\"\n    ]\n}"}],"_postman_id":"844970ba-cb9a-4b70-bf74-34a37749e54e"},{"name":"SCIM Replace Group","id":"234da112-61e5-4632-ba4f-85c10c2f72a0","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:Group\"\n    ],\n    \"displayName\": \"emea analysts\",\n    \"members\": [\n        {\n            \"value\": \"{{scim_user_id}}\"\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups/{{scim_group_id}}","description":"<p>Replaces the name and all members of a group.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n<li><strong><code>scim_group_id</code></strong> (string): The group <code>id</code>.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p><code>{\"status\": \"updated\"}</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Groups","{{scim_group_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"6a53c6b3-1ab0-4b6d-b2c7-75a8266ceb46","name":"SCIM Replace Group - 200 OK","originalRequest":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:Group\"\n    ],\n    \"displayName\": \"emea analysts\",\n    \"members\": [\n        {\n            \"value\": \"{{scim_user_id}}\"\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups/{{scim_group_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"status\": \"updated\"\n}"}],"_postman_id":"234da112-61e5-4632-ba4f-85c10c2f72a0"},{"name":"SCIM Update Group","id":"37ab3912-d546-4bdf-bd45-faaac48a6805","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:api:messages:2.0:PatchOp\"\n    ],\n    \"Operations\": [\n        {\n            \"op\": \"add\",\n            \"path\": \"members\",\n            \"value\": [\n                {\n                    \"value\": \"{{scim_user_id}}\"\n                }\n            ]\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups/{{scim_group_id}}","description":"<p>Adds or removes group members.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n<li><strong><code>scim_group_id</code></strong> (string): The group <code>id</code>.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>A SCIM PatchOp. Supported operations:</p>\n<ul>\n<li>Add: <code>{\"op\": \"add\", \"path\": \"members\", \"value\": [{\"value\": \"&lt;user id&gt;\"}]}</code></li>\n<li>Replace all: <code>{\"op\": \"replace\", \"path\": \"members\", \"value\": [...]}</code></li>\n<li>Remove one: <code>{\"op\": \"remove\", \"path\": \"members[value eq \\\"&lt;user id&gt;\\\"]\"}</code></li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The updated group.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Groups","{{scim_group_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"822b96ae-c0b3-4514-ab73-5f09abc1e11f","name":"SCIM Update Group - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"schemas\": [\n        \"urn:ietf:params:scim:api:messages:2.0:PatchOp\"\n    ],\n    \"Operations\": [\n        {\n            \"op\": \"add\",\n            \"path\": \"members\",\n            \"value\": [\n                {\n                    \"value\": \"{{scim_user_id}}\"\n                }\n            ]\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups/{{scim_group_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"8eac2e35-1066-5c68-a3f6-eebe796e5077\",\n    \"displayName\": \"emea analysts\",\n    \"members\": [\n        {\n            \"value\": \"50e32e80-0397-5f0a-b129-b2bf880ecd96\",\n            \"$ref\": \"\",\n            \"display\": \"dana.levi@example.com\"\n        }\n    ],\n    \"meta\": {\n        \"resourceType\": \"Group\",\n        \"created\": \"2026-10-30T01:08:07.257351Z\",\n        \"lastModified\": \"2026-10-30T01:08:07.438465Z\"\n    },\n    \"schemas\": [\n        \"urn:ietf:params:scim:schemas:core:2.0:Group\"\n    ]\n}"}],"_postman_id":"37ab3912-d546-4bdf-bd45-faaac48a6805"},{"name":"SCIM Delete Group","id":"f98341fc-4af5-405f-850f-1183a37d3f28","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups/{{scim_group_id}}","description":"<p>Deletes the group. The users stay in the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The workspace ID.</li>\n<li><strong><code>sso_integration_id</code></strong> (string): The SSO provider ID.</li>\n<li><strong><code>scim_group_id</code></strong> (string): The group <code>id</code>.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","oauth2","{{sso_integration_id}}","scim","v2","Groups","{{scim_group_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"5933e1d6-945f-45a2-8eac-7269386173a1","name":"SCIM Delete Group - 200 OK","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/oauth2/{{sso_integration_id}}/scim/v2/Groups/{{scim_group_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"status\": \"deleted\"\n}"}],"_postman_id":"f98341fc-4af5-405f-850f-1183a37d3f28"}],"id":"fba1dc18-957d-42f9-bdf9-a647770033b3","description":"<p><strong>SCIM</strong> keeps PVML users and groups in sync with your identity provider (IdP), such as Microsoft Entra ID, Okta or SailPoint. When someone joins, moves or leaves in your IdP, PVML follows automatically. You do not call these endpoints yourself: your IdP does.</p>\n<h3 id=\"set-up-scim\">Set up SCIM</h3>\n<p><strong>In the PVML UI (easiest):</strong> open <strong>Settings &gt; SSO</strong>, open your SSO provider and turn on SCIM provisioning. PVML shows a <strong>Base URL</strong> and an <strong>Auth token</strong>. Put them in the provisioning settings of your IdP.</p>\n<p><strong>With the API:</strong></p>\n<ol>\n<li>Pick the SSO provider of the workspace (<strong>Get SSO Providers</strong>). Its <code>id</code> is part of the URL.</li>\n<li>Create a service account for the IdP (<strong>Create Service Account</strong>) and give it the <code>admin</code> role (<strong>Update User Role</strong>).</li>\n<li>Create an API key for this service account (<strong>Create User API Key</strong>). This is the token.</li>\n<li>In your IdP, set:<ul>\n<li><strong>Base URL / Tenant URL:</strong> <code>https://platform.pvml.com/platform/api/v1/workspaces/{workspace_id}/oauth2/{sso_integration_id}/scim/v2</code></li>\n<li><strong>Authentication:</strong> Bearer token, with the API key.</li>\n</ul>\n</li>\n</ol>\n<p>💡 <strong>Why a separate service account?</strong> The IdP gets its own key, which you can revoke at any time without touching any person's access. In the activity log you also see clearly which changes came from the IdP.</p>\n<h3 id=\"what-pvml-supports\">What PVML supports</h3>\n<ul>\n<li><strong>Users:</strong> create, read, list, replace (PUT), update (PATCH) and delete. <code>userName</code> is the email address.</li>\n<li><strong>Groups:</strong> create, read, list, replace (PUT), update members (PATCH) and delete.</li>\n<li><strong>Filters:</strong> Users by <code>userName</code> (<code>eq</code>, <code>ew</code>), <code>externalId</code> (<code>eq</code>) and <code>id</code> (<code>eq</code>). Groups by <code>displayName</code> (<code>eq</code>) and <code>id</code> (<code>eq</code>). Combine with <code>and</code>. Example: <code>filter=userName eq \"dana@example.com\"</code>.</li>\n<li><strong>Pages:</strong> <code>startIndex</code> (default 1) and <code>count</code> (default 100).</li>\n<li><strong>Deactivate a user:</strong> PATCH <code>active</code> to <code>false</code>. Use it when someone leaves: the user is deactivated, not deleted.</li>\n<li><strong>Admin role:</strong> a <code>roles</code> entry with the value <code>admin</code> makes the user a workspace admin.</li>\n</ul>\n<h3 id=\"good-to-know\">Good to know</h3>\n<ul>\n<li>Add group members with <strong>SCIM Update Group (PATCH)</strong>, operation <code>add</code> and path <code>members</code>.</li>\n<li>Not supported: bulk requests, sorting, ETags and password changes.</li>\n</ul>\n","_postman_id":"fba1dc18-957d-42f9-bdf9-a647770033b3","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}}],"id":"7692889e-4205-415f-96a9-e02a11a98512","description":"<p>Everything in PVML is tied to a specific workspace. A workspace contains your datasources, users, policies, and a full audit log.</p>\n<p>To get your workspace id, go to <strong><code>System &gt; Settings &gt; General</code></strong> and copy the <strong>workspace id</strong>:</p>\n<img src=\"https://content.pstmn.io/ce3eda96-2f70-4347-bf19-3b12af7413a4/VW50aXRsZWQgZGVzaWduICgxKS5naWY=\" alt=\"PVML%20-%20Get%20Workspace%20ID\" />\n\n","_postman_id":"7692889e-4205-415f-96a9-e02a11a98512","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Users","item":[{"name":"Groups","item":[{"name":"Create Group","id":"ca71ee4e-95c4-45e4-9c00-5ce6613ea3e1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\"name\": \"new group\",\n\"description\": \"some description\"}"},{"key":"image","type":"file","value":null}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups","description":"<p>This endpoint allows to create a new group in a specific workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><code>workspace_id</code>(string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>Form data with these fields:</p>\n<ul>\n<li><p><code>data</code> (text): Json with the following properties:</p>\n<ul>\n<li><p><code>name</code>(string): The name of the group.</p>\n</li>\n<li><p><code>description</code>(string): The description of the group.</p>\n</li>\n</ul>\n</li>\n<li><p><code>image</code> (File): The image for the group.</p>\n</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 201 with the new <strong><code>group</code></strong>: <code>id</code>, <code>name</code>, <code>description</code>, <code>workspaceId</code>, <code>createdAt</code>, <code>lastModifiedAt</code> and <code>image</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"da754dfb-372b-4efc-a453-2b61235ed931","name":"Create Group - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\"name\": \"new group\",\n\"description\": \"some description\"}"},{"key":"image","type":"file","src":[]}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"group\": {\n        \"id\": \"d8c07ec8-f3e6-5482-99c3-e708d18bebab\",\n        \"name\": \"analysts\",\n        \"description\": \"Data analysts team\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"createdAt\": \"2026-10-29T23:55:56.252183Z\",\n        \"lastModifiedAt\": \"2026-10-29T23:55:56.252183Z\",\n        \"image\": \"\"\n    }\n}"}],"_postman_id":"ca71ee4e-95c4-45e4-9c00-5ce6613ea3e1"},{"name":"Get Group","id":"f2fb50aa-ab10-4dba-9a3a-fe610f306a79","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}?enrich=false","description":"<p>This endpoint retrieves a specific group within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>group_id</code> (string): The unique identifier of the group.</p>\n</li>\n<li><p><code>enrich</code> (boolean, query parameter): when set to <code>true</code>, the request returns additional details such as the group's image and users count.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><p><code>group</code>:</p>\n<ul>\n<li><p><code>id</code> (string): Unique identifier for the group.</p>\n</li>\n<li><p><code>name</code> (string): The name of the group.</p>\n</li>\n<li><p><code>description</code> (string): Description of the group.</p>\n</li>\n<li><p><code>workspaceId</code> (string): Unique identifier for the workspace to which the group belongs.</p>\n</li>\n<li><p><code>createdAt</code> (string, ISO 8601 format) Timestamp when the group was created.</p>\n</li>\n<li><p><code>lastModifiedAt</code> (string, ISO 8601 format): Timestamp when the group was last modified.</p>\n</li>\n<li><p><code>usersCount</code> (int): Number of users that belong to this group (returned if <code>enrich</code> is <code>true</code>).</p>\n</li>\n<li><p><code>image</code> (Base64-encoded string): binary image data; decode to retrieve the original image (returned if <code>enrich</code> is <code>true</code>).</p>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}"],"host":["platform","pvml","com"],"query":[{"key":"enrich","value":"false"}],"variable":[]}},"response":[{"id":"c36b5048-d9ed-45ac-8578-52a511555279","name":"Get Group - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}?enrich=false","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}"],"query":[{"key":"enrich","value":"false"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"group\": {\n        \"id\": \"d8c07ec8-f3e6-5482-99c3-e708d18bebab\",\n        \"name\": \"analysts\",\n        \"description\": \"Data analysts team\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"createdAt\": \"2026-10-29T23:55:56.252183Z\",\n        \"lastModifiedAt\": \"2026-10-29T23:55:56.430034Z\"\n    }\n}"}],"_postman_id":"f2fb50aa-ab10-4dba-9a3a-fe610f306a79"},{"name":"Get Groups","id":"600e2452-6543-4ee7-8aa8-324d5eebc4b5","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups?summary=true","description":"<p>This endpoint retrieves the groups within a specific workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>summary</code> (boolean, query parameter): when set to <code>true</code>, the request returns additional details such as each group's image and users count.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><p><code>groups</code><strong>:</strong> the groups of the workspace</p>\n<ul>\n<li><p><code>group</code>:</p>\n<ul>\n<li><p><code>id</code>: (String) Unique identifier for the group.</p>\n</li>\n<li><p><code>name</code> (string): The name of the group.</p>\n</li>\n<li><p><code>description</code> (string): Description of the group.</p>\n</li>\n<li><p><code>workspaceId</code> (string): Unique identifier for the workspace to which the group belongs.</p>\n</li>\n<li><p><code>createdAt</code>: (string, ISO 8601 format) Timestamp when the group was created.</p>\n</li>\n<li><p><code>lastModifiedAt</code>: (string, ISO 8601 format) Timestamp when the group was last modified.</p>\n</li>\n<li><p><code>usersCount</code> (int): Number of users that belong to this group (returned if <code>summary</code> is <code>true</code>).</p>\n</li>\n<li><p><code>image</code> (Base64-encoded string): binary image data; decode to retrieve the original image (returned if <code>summary</code> is <code>true</code>).</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<p>💡 The response also has a <strong><code>pagination</code></strong> object: <code>totalRecords</code>, <code>currentPage</code>, <code>totalPages</code>, <code>pageSize</code>, <code>nextPage</code>, <code>prevPage</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups"],"host":["platform","pvml","com"],"query":[{"key":"summary","value":"true"}],"variable":[]}},"response":[{"id":"949382ca-a3f3-4771-ac4a-004fc251ffd8","name":"Get Groups - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups?summary=true","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","groups"],"query":[{"key":"summary","value":"true"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"groups\": [\n        {\n            \"id\": \"d8c07ec8-f3e6-5482-99c3-e708d18bebab\",\n            \"name\": \"analysts\",\n            \"description\": \"Data analysts team\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"createdAt\": \"2026-10-29T23:55:56.252183Z\",\n            \"lastModifiedAt\": \"2026-10-29T23:55:56.430034Z\",\n            \"usersCount\": 1,\n            \"image\": \"\"\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 1,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 1,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"600e2452-6543-4ee7-8aa8-324d5eebc4b5"},{"name":"Get Group VDBs","id":"c2e72fd0-32c2-4e63-875c-f37ed657bc35","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/vdbs","description":"<p>This endpoint returns the VDBs that a group can access within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>group_id</code> (string): The unique identifier of the group.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><p><code>vdbs</code><strong>:</strong> a list of VDBs associated with the group.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>  - `vdbId` (string): Unique identifier for the VDB.\n      \n  - `datasourceId` (string): Unique identifier for the datasource of the VDB.\n      \n  - `datasourceType` (string): The type of the datasource (e.g., \"Postgres\").\n      \n  - `workspaceId` (string): Unique identifier for the workspace to which the group belongs.\n      \n  - `name` (string): The name of the VDB.\n      \n  - `description` (string): Description of the VDB.\n      \n  - `createdAt` (string, ISO 8601 format): Timestamp when the group was created.\n      \n  - `lastModifiedAt` (string, ISO 8601 format): Timestamp when the group was last modified.\n      \n  - `permissionsLastModifiedAt` (string, ISO 8601 format): Timestamp when the policies attached to the VDB were last modified.\n      \n  - `image` (Base64-encoded string): binary image data; decode to retrieve the original image (returned if `enrich` is `true`).\n</code></pre></li>\n</ul>\n<p>💡 The response also has a <strong><code>pagination</code></strong> object: <code>totalRecords</code>, <code>currentPage</code>, <code>totalPages</code>, <code>pageSize</code>, <code>nextPage</code>, <code>prevPage</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}","vdbs"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"b68f3928-3585-4423-b6ea-5fbfa02889aa","name":"Get Group VDBs - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/vdbs"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"vdbs\": [\n        {\n            \"vdbId\": \"df8fe2c3-7c75-5f3e-8d47-d08181298485\",\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceType\": \"BigQuery\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"name\": \"clinic analytics\",\n            \"description\": \"VDB for the clinic analytics team\",\n            \"shouldEnforce\": false,\n            \"permissionsLastModifiedAt\": 1793318159168,\n            \"createdAt\": 1793318158530,\n            \"lastModifiedAt\": 1793318159171\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 1,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 1,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"c2e72fd0-32c2-4e63-875c-f37ed657bc35"},{"name":"Get Group Users","id":"5c063a45-b5b2-4983-a88b-ea0fc4509e0b","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/users","description":"<p>This endpoint retrieves the users of a specific group within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>group_id</code> (string): The unique identifier of the group.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><p><code>users</code><strong>:</strong> a list of users associated with the group.</p>\n<ul>\n<li><p><code>User</code>:</p>\n<ul>\n<li><p><code>id</code> (string): Unique identifier for the user.</p>\n</li>\n<li><p><code>email</code> (string): Email address of the user.</p>\n</li>\n<li><p><code>created_at</code> (string, ISO 8601 format): Timestamp when the user was created/joined.</p>\n</li>\n<li><p><code>image</code> (string, URL): Link to the user’s profile picture or avatar.</p>\n</li>\n</ul>\n</li>\n<li><p><code>workspaceId</code> (string): Unique identifier for the workspace to which the user belongs.</p>\n</li>\n<li><p><code>joined_at</code> (string, ISO 8601 format): Timestamp when the user joined the workspace.</p>\n</li>\n<li><p><code>role</code> (string): Role of the user in the workspace: \"owner,\" \"admin,\" or \"member.\"</p>\n</li>\n<li><p><code>type</code> (string): The type of the user in the workspace: <code>regular</code> (platform user) or <code>adhoc</code> (service account).</p>\n</li>\n</ul>\n</li>\n</ul>\n<p>💡 The response also has a <strong><code>pagination</code></strong> object: <code>totalRecords</code>, <code>currentPage</code>, <code>totalPages</code>, <code>pageSize</code>, <code>nextPage</code>, <code>prevPage</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}","users"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"cfef9353-b0e1-42f0-b2de-16b44a22f5ac","name":"Get Group Users - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/users"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"users\": [\n        {\n            \"User\": {\n                \"id\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n                \"email\": \"jane.doe@example.com\",\n                \"created_at\": \"2026-10-29T23:26:19.455862Z\"\n            },\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"joined_at\": \"0001-01-01T00:00:00Z\",\n            \"role\": \"owner\",\n            \"type\": \"regular\"\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 1,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 1,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"5c063a45-b5b2-4983-a88b-ea0fc4509e0b"},{"name":"Update Group Info","id":"5728961c-5530-4206-affb-97030a84b52a","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"name\":\"new name\"\n}"},{"key":"description","type":"text","value":"","disabled":true},{"key":"image","type":"file","value":null,"disabled":true}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}","description":"<p>This endpoint allows to update a specific group in a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>group_id</code>(string): the unique id of the group in the workspace</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>Form data containing the fields to update:</p>\n<ul>\n<li><p><code>data</code> (text): Json with the following properties:</p>\n<ul>\n<li><p><code>name</code>(string): The name of the group.</p>\n</li>\n<li><p><code>description</code>(string): The description of the group.</p>\n</li>\n</ul>\n</li>\n<li><p><code>image</code> (File): The image for the group.</p>\n</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Upon successful update, the server returns a status code of 200 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"124859b5-a699-495e-898c-48c1cc6ad51d","name":"Update Group Info - 200 Success","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"name\":\"new name\"\n}"},{"key":"description","type":"text","value":"","disabled":true},{"key":"image","type":"file","src":[],"disabled":true}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"\"\""}],"_postman_id":"5728961c-5530-4206-affb-97030a84b52a"},{"name":"Update Group Users","id":"3666cd14-3b81-4137-8443-e95f00c6f445","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"usersToAdd\": [\n        \"{{user_id}}\"\n    ],\n    \"usersToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/users","description":"<p>Adds users to a group and removes users from it.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>group_id</code>(string): the unique id of the group in the workspace</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><code>usersToAdd</code> (array of strings): The IDs of the users to add.</li>\n<li><code>usersToRemove</code> (array of strings): The IDs of the users to remove.</li>\n</ul>\n<p>Each array can be empty (<code>[]</code>).</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Upon successful update, the server returns a status code of 200 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}","users"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"a24e39ca-51a4-496a-a095-c0884610942c","name":"Update Group Users - 200 Success","originalRequest":{"method":"PATCH","header":[{"key":"Content-Type","type":"text","value":"application/json"}],"body":{"mode":"raw","raw":"{\n    \"usersToAdd\": [\n        \"{{user_id}}\"\n    ],\n    \"usersToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/users"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"\"\""}],"_postman_id":"3666cd14-3b81-4137-8443-e95f00c6f445"},{"name":"Update Group VDBs","id":"f5aea157-b9f3-4d48-8066-642c732a7896","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"vdbsToAdd\": [\n        \"{{vdb_id}}\"\n    ],\n    \"vdbsToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/vdbs","description":"<p>Gives a group access to VDBs, or removes it. All users in the group get the access.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>group_id</code></strong> (string): The group ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>vdbsToAdd</code></strong> (array of strings, required): VDB IDs.</li>\n<li><strong><code>vdbsToRemove</code></strong> (array of strings, required): VDB IDs.</li>\n</ul>\n<p>Send both keys. Use <code>[]</code> for an empty list.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}","vdbs"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"74533bbc-39aa-4930-8af6-491a94174073","name":"Update Group VDBs - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"vdbsToAdd\": [\n        \"{{vdb_id}}\"\n    ],\n    \"vdbsToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/vdbs"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"f5aea157-b9f3-4d48-8066-642c732a7896"},{"name":"Delete Group","id":"0e6c02b9-a5b8-411e-9e70-d1356ca5b843","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}","description":"<p>This endpoint deletes a specific group within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>group_id</code> (string): The unique identifier of the group.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Upon successful deletion of the group from the workspace, the server returns a status code of 200 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"1cf5123e-30d2-4562-b7f7-b2ced39c95b0","name":"Delete Group - 200 Success","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"\"\""}],"_postman_id":"0e6c02b9-a5b8-411e-9e70-d1356ca5b843"}],"id":"da074097-cd8b-401f-bb9a-2c94996b9e3a","description":"<p>A group is a collection of users, typically organized based on a shared purpose such as team membership, department, or role within the organization.</p>\n<p>Groups provide a convenient way to assign permissions to multiple users simultaneously, enabling consistent and efficient access management aligned with business use cases.</p>\n","_postman_id":"da074097-cd8b-401f-bb9a-2c94996b9e3a","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Attributes","item":[{"name":"Create Attribute Catalog","id":"61fe4aa7-478a-4ed1-9c3b-b3c1d6f1e5bb","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"hr\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs","description":"<p>Creates an attribute catalog. A catalog is a named group of attribute definitions, for example <code>hr</code> or <code>bank</code>. The catalog name is the first part of the attribute name in policies: <code>{{hr.region}}</code>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>name</code></strong> (string, required): The catalog name. It can not contain a dot.</li>\n<li><strong><code>ssoProviderId</code></strong> (string, optional): Link the catalog to an SSO provider. Then attribute values come from your identity provider through SCIM. See <strong>Workspaces &gt; Guide: SSO and SCIM setup</strong>.</li>\n<li><strong><code>urn</code></strong> (string, optional): The prefix your identity provider uses for these attributes, for example a SCIM extension URN (<code>urn:...</code>). PVML uses it to match incoming attribute values to the definitions in this catalog.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The catalog: <code>id</code>, <code>workspaceId</code>, <code>name</code>, <code>isSystem</code>, and <code>ssoProviderId</code> / <code>urn</code> if set. System catalogs are created by PVML and can not be changed.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","catalogs"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"15bd80bc-6891-4932-8dc2-3412d419a875","name":"Create Attribute Catalog - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"hr\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"name\": \"hr\",\n    \"isSystem\": false\n}"}],"_postman_id":"61fe4aa7-478a-4ed1-9c3b-b3c1d6f1e5bb"},{"name":"Get Attribute Catalogs","id":"f4eae6df-4296-417f-a8ff-02879f3b54b6","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs","description":"<p>Lists the attribute catalogs of the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>catalogs</code></strong> (array): Catalog objects.</li>\n<li><strong><code>pagination</code></strong> (object).</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","catalogs"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"788c8185-8822-42cc-9423-081904b26e8e","name":"Get Attribute Catalogs - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"catalogs\": [\n        {\n            \"id\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"name\": \"hr\",\n            \"isSystem\": false\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 1,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 1,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"f4eae6df-4296-417f-a8ff-02879f3b54b6"},{"name":"Get Attribute Catalog","id":"40712b93-b027-449a-a7d7-95f234e30678","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}","description":"<p>Returns one attribute catalog.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>catalog_id</code></strong> (string): The attribute catalog ID.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","catalogs","{{catalog_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"a767ee81-37c2-49f1-9cbd-d9ee0ca32117","name":"Get Attribute Catalog - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"name\": \"hr\",\n    \"isSystem\": false\n}"}],"_postman_id":"40712b93-b027-449a-a7d7-95f234e30678"},{"name":"Update Attribute Catalog","id":"7faad350-ff49-4a3f-a8c2-e5eec57aa44a","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"hr\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}","description":"<p>Updates the name, SSO provider or URN of a catalog.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>catalog_id</code></strong> (string): The attribute catalog ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>The same fields as <strong>Create Attribute Catalog</strong>.</p>\n<p>⚠️ Renaming a catalog changes the attribute names (<code>{{old.x}}</code> becomes <code>{{new.x}}</code>). Update your row policies too.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","catalogs","{{catalog_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"468f8c90-0dda-4f22-bd45-81aa4b782458","name":"Update Attribute Catalog - 200 OK","originalRequest":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"hr\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"name\": \"hr\",\n    \"isSystem\": false\n}"}],"_postman_id":"7faad350-ff49-4a3f-a8c2-e5eec57aa44a"},{"name":"Delete Attribute Catalog","id":"651bb177-b570-487d-b987-059effa9e700","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}","description":"<p>Deletes an attribute catalog.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>catalog_id</code></strong> (string): The attribute catalog ID.</li>\n</ul>\n<p>⚠️ The catalog must be empty. Remove all its attribute definitions first (with <strong>Define Attributes</strong>, <code>attributesToRemove</code>).</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","catalogs","{{catalog_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"bbae0f9f-3e2b-4623-a44a-fdb6df71cc48","name":"Delete Attribute Catalog - 200 OK","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"651bb177-b570-487d-b987-059effa9e700"},{"name":"Define Attributes","id":"60d4c4c2-f073-4ad7-abad-ea1b7f198101","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"attributesToUpsert\": [\n        {\n            \"name\": \"region\",\n            \"type\": \"text\"\n        },\n        {\n            \"name\": \"branch_ids\",\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"numeric\",\n                \"multiValued\": true\n            }\n        },\n        {\n            \"name\": \"max_amount\",\n            \"type\": \"numeric\"\n        },\n        {\n            \"name\": \"office\",\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"complex\",\n                \"multiValued\": false,\n                \"subAttributes\": [\n                    {\n                        \"name\": \"city\",\n                        \"type\": \"text\"\n                    },\n                    {\n                        \"name\": \"country\",\n                        \"type\": \"text\"\n                    }\n                ]\n            }\n        }\n    ],\n    \"attributesToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}/attributes","description":"<p>Adds, changes or removes attribute definitions in a catalog. A definition sets the name and type of an attribute. Values are set later per user or group.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>catalog_id</code></strong> (string): The attribute catalog ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>attributesToUpsert</code></strong> (array): Definitions to add or change. Each item has:<ul>\n<li><strong><code>name</code></strong> (string, required): The attribute name. It can not contain a dot.</li>\n<li><strong><code>type</code></strong> (string, required): <code>text</code>, <code>numeric</code>, <code>boolean</code>, <code>date</code>, <code>datetime</code>, <code>timestamp</code>, <code>geometry</code> or <code>complex</code>.</li>\n<li><strong><code>complexType</code></strong> (object): Only for <code>complex</code>:<ul>\n<li><strong><code>type</code></strong> (string): The type of each item (for an array) or <code>complex</code> (for an object).</li>\n<li><strong><code>multiValued</code></strong> (boolean): <code>true</code> for an array, <code>false</code> for an object.</li>\n<li><strong><code>subAttributes</code></strong> (array): For an object, its fields: <code>{\"name\", \"type\"}</code>.</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><strong><code>attributesToRemove</code></strong> (array of strings): Attribute definition IDs to remove.</li>\n</ul>\n<h4 id=\"attribute-types\">Attribute types</h4>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Type</th>\n<th>Example value</th>\n<th>How it looks in SQL</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>text</code></td>\n<td><code>\"London\"</code></td>\n<td><code>'London'</code></td>\n</tr>\n<tr>\n<td><code>numeric</code></td>\n<td><code>50000</code></td>\n<td><code>50000</code></td>\n</tr>\n<tr>\n<td><code>boolean</code></td>\n<td><code>true</code></td>\n<td><code>true</code></td>\n</tr>\n<tr>\n<td><code>date</code> / <code>datetime</code> / <code>timestamp</code></td>\n<td><code>\"2026-01-31\"</code></td>\n<td>a date or time literal for your database</td>\n</tr>\n<tr>\n<td><code>geometry</code></td>\n<td>a WKT string</td>\n<td>a geometry literal for your database</td>\n</tr>\n<tr>\n<td><code>complex</code>, <code>multiValued: true</code> (array)</td>\n<td><code>[\"London\", \"Paris\"]</code></td>\n<td><code>'London','Paris'</code> (use it inside <code>IN (...)</code>)</td>\n</tr>\n<tr>\n<td><code>complex</code>, <code>multiValued: false</code> (object)</td>\n<td><code>{\"city\": \"London\", \"country\": \"GBR\"}</code></td>\n<td>use one field: <code>{{catalog.office.city}}</code></td>\n</tr>\n</tbody>\n</table>\n</div><h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>definitions</code></strong> (array): The saved definitions, each with its <code>id</code>. Use the <code>id</code> as <code>attributeId</code> when you set values.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","catalogs","{{catalog_id}}","attributes"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"8387b6f0-bfea-47b6-91d3-b4bda2f55223","name":"Define Attributes - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"attributesToUpsert\": [\n        {\n            \"name\": \"region\",\n            \"type\": \"text\"\n        },\n        {\n            \"name\": \"branch_ids\",\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"numeric\",\n                \"multiValued\": true\n            }\n        },\n        {\n            \"name\": \"max_amount\",\n            \"type\": \"numeric\"\n        },\n        {\n            \"name\": \"office\",\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"complex\",\n                \"multiValued\": false,\n                \"subAttributes\": [\n                    {\n                        \"name\": \"city\",\n                        \"type\": \"text\"\n                    },\n                    {\n                        \"name\": \"country\",\n                        \"type\": \"text\"\n                    }\n                ]\n            }\n        }\n    ],\n    \"attributesToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}/attributes"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"definitions\": [\n        {\n            \"id\": \"913031ce-7fc9-5ada-aca3-5b03d7a31e4f\",\n            \"catalogId\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n            \"name\": \"region\",\n            \"type\": \"text\"\n        },\n        {\n            \"id\": \"f0039ce3-3066-53fb-aa2f-ac06cbc2d4a7\",\n            \"catalogId\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n            \"name\": \"branch_ids\",\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"numeric\",\n                \"multiValued\": true\n            }\n        },\n        {\n            \"id\": \"a7e38105-51f2-5996-bfa4-1902122303fe\",\n            \"catalogId\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n            \"name\": \"max_amount\",\n            \"type\": \"numeric\"\n        },\n        {\n            \"id\": \"a4a4a6f3-d2df-5a02-97c1-b467673f32e9\",\n            \"catalogId\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n            \"name\": \"office\",\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"complex\",\n                \"multiValued\": false,\n                \"subAttributes\": [\n                    {\n                        \"name\": \"city\",\n                        \"type\": \"text\"\n                    },\n                    {\n                        \"name\": \"country\",\n                        \"type\": \"text\"\n                    }\n                ]\n            }\n        }\n    ]\n}"}],"_postman_id":"60d4c4c2-f073-4ad7-abad-ea1b7f198101"},{"name":"Get Catalog Attributes","id":"63561b31-a900-4210-8dfe-7559032deb01","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}/attributes","description":"<p>Lists the attribute definitions of a catalog.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>catalog_id</code></strong> (string): The attribute catalog ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>definitions</code></strong> (array): <code>id</code>, <code>catalogId</code>, <code>name</code>, <code>type</code>, <code>complexType</code>.</li>\n<li><strong><code>pagination</code></strong> (object).</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","catalogs","{{catalog_id}}","attributes"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"41b681d8-16f9-4d63-9a0e-d0adc484d9a4","name":"Get Catalog Attributes - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}/attributes"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"definitions\": [\n        {\n            \"id\": \"f0039ce3-3066-53fb-aa2f-ac06cbc2d4a7\",\n            \"catalogId\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n            \"name\": \"branch_ids\",\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"numeric\",\n                \"multiValued\": true\n            }\n        },\n        {\n            \"id\": \"a7e38105-51f2-5996-bfa4-1902122303fe\",\n            \"catalogId\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n            \"name\": \"max_amount\",\n            \"type\": \"numeric\"\n        },\n        {\n            \"id\": \"a4a4a6f3-d2df-5a02-97c1-b467673f32e9\",\n            \"catalogId\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n            \"name\": \"office\",\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"complex\",\n                \"multiValued\": false,\n                \"subAttributes\": [\n                    {\n                        \"name\": \"city\",\n                        \"type\": \"text\"\n                    },\n                    {\n                        \"name\": \"country\",\n                        \"type\": \"text\"\n                    }\n                ]\n            }\n        },\n        {\n            \"id\": \"913031ce-7fc9-5ada-aca3-5b03d7a31e4f\",\n            \"catalogId\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n            \"name\": \"region\",\n            \"type\": \"text\"\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 4,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 4,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"63561b31-a900-4210-8dfe-7559032deb01"},{"name":"Get Catalog Attribute","id":"5ff4fd03-a236-425a-8dff-a6ffeddd2b27","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}/attributes/{{attribute_id}}","description":"<p>Returns one attribute definition.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>catalog_id</code></strong> (string): The attribute catalog ID.</li>\n<li><strong><code>attribute_id</code></strong> (string): The attribute definition ID.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","catalogs","{{catalog_id}}","attributes","{{attribute_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"b3dbbb15-df5b-4993-8b6c-7e20a79fbf79","name":"Get Catalog Attribute - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/catalogs/{{catalog_id}}/attributes/{{attribute_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"913031ce-7fc9-5ada-aca3-5b03d7a31e4f\",\n    \"catalogId\": \"8477be2f-ebf0-5b82-b3b3-d59cbda9ce24\",\n    \"name\": \"region\",\n    \"type\": \"text\"\n}"}],"_postman_id":"5ff4fd03-a236-425a-8dff-a6ffeddd2b27"},{"name":"Get Attribute Names","id":"4239e71b-45d8-4785-87ba-0188de365e19","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/attributes","description":"<p>Lists the full names of all attributes in the workspace, as you write them in a row policy (<code>catalog.attribute</code>).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>keys</code></strong> (array of strings): For example <code>hr.region</code>. Use them as <code>{{hr.region}}</code>.</li>\n<li><strong><code>pagination</code></strong> (object).</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","attributes"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"fff431c3-b75c-4ca9-bd56-b12ed8497d40","name":"Get Attribute Names - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/attributes"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"keys\": [\n        \"hr.branch_ids\",\n        \"hr.max_amount\",\n        \"hr.office\",\n        \"hr.region\"\n    ],\n    \"pagination\": {\n        \"totalRecords\": 4,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 4,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"4239e71b-45d8-4785-87ba-0188de365e19"},{"name":"Get Attributes Metadata","id":"519c8bb9-ac2b-4d3c-be08-4d3f0fae4ad0","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/attributes/metadata","description":"<p>Lists all attributes in the workspace with their types.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>attributesMetadata</code></strong> (array): <code>key</code> (full name), <code>type</code>, <code>complexType</code>.</li>\n<li><strong><code>pagination</code></strong> (object).</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","attributes","metadata"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"4488f19f-6444-4e7d-b835-dde4473c0312","name":"Get Attributes Metadata - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/attributes/metadata"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"attributesMetadata\": [\n        {\n            \"key\": \"hr.branch_ids\",\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"numeric\",\n                \"multiValued\": true\n            }\n        },\n        {\n            \"key\": \"hr.max_amount\",\n            \"type\": \"numeric\"\n        },\n        {\n            \"key\": \"hr.office\",\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"complex\",\n                \"multiValued\": false,\n                \"subAttributes\": [\n                    {\n                        \"name\": \"city\",\n                        \"type\": \"text\"\n                    },\n                    {\n                        \"name\": \"country\",\n                        \"type\": \"text\"\n                    }\n                ]\n            }\n        },\n        {\n            \"key\": \"hr.region\",\n            \"type\": \"text\"\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 4,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 4,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"519c8bb9-ac2b-4d3c-be08-4d3f0fae4ad0"},{"name":"Set User Attributes","id":"5094ad9a-896e-4343-8160-8cc3ea36a402","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"attributesToUpsert\": [\n        {\n            \"attributeId\": \"{{attribute_id}}\",\n            \"value\": \"EMEA\"\n        }\n    ],\n    \"attributesToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/attributes","description":"<p>Sets or removes attribute values for one user. Row policies use these values when this user runs a query.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): The user ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>attributesToUpsert</code></strong> (array): Values to set. Each item has:<ul>\n<li><strong><code>attributeId</code></strong> (string): The attribute definition ID (from <strong>Define Attributes</strong>).</li>\n<li><strong><code>value</code></strong>: The value, in the format of the attribute type (see <strong>Define Attributes</strong>). For example <code>\"EMEA\"</code>, <code>10000</code>, <code>[\"London\", \"Paris\"]</code> or <code>{\"city\": \"London\", \"country\": \"GBR\"}</code>.</li>\n</ul>\n</li>\n<li><strong><code>attributesToRemove</code></strong> (array of strings): Attribute definition IDs whose value you want to remove.</li>\n</ul>\n<p>💡 A user value wins over a group value of the same attribute.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","attributes"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"c94938ac-2328-483e-85c3-86b0923a0754","name":"Set User Attributes - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"attributesToUpsert\": [\n        {\n            \"attributeId\": \"{{attribute_id}}\",\n            \"value\": \"EMEA\"\n        }\n    ],\n    \"attributesToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/attributes"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"5094ad9a-896e-4343-8160-8cc3ea36a402"},{"name":"Get User Attributes","id":"3a01f092-37aa-492a-bd3f-812fb3495990","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/attributes","description":"<p>Lists the attribute values of a user.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): The user ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>attributes</code></strong> (array). Each item has:<ul>\n<li><strong><code>id</code></strong> (string): The ID of this <strong>value</strong>. Use it in <strong>Get User Attribute</strong> and <strong>Delete User Attribute</strong>.</li>\n<li><strong><code>attributeId</code></strong> (string): The attribute definition ID.</li>\n<li><strong><code>name</code></strong>, <strong><code>catalogName</code></strong>, <strong><code>type</code></strong>, <strong><code>complexType</code></strong>, <strong><code>value</code></strong>.</li>\n<li><strong><code>ssoProviderId</code></strong> (string): Set when the value comes from your identity provider.</li>\n</ul>\n</li>\n<li><strong><code>pagination</code></strong> (object).</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","attributes"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"2ff303d6-af32-4a5e-be27-fad3a9eb6575","name":"Get User Attributes - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/attributes"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"attributes\": [\n        {\n            \"id\": \"994ceff3-d747-5736-b7e0-fe8bb93f4149\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"userId\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n            \"attributeId\": \"f0039ce3-3066-53fb-aa2f-ac06cbc2d4a7\",\n            \"name\": \"branch_ids\",\n            \"value\": [\n                110,\n                111\n            ],\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"numeric\",\n                \"multiValued\": true\n            },\n            \"catalogName\": \"hr\"\n        },\n        {\n            \"id\": \"a565fa24-1738-55a8-b682-76455aa345a4\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"userId\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n            \"attributeId\": \"a4a4a6f3-d2df-5a02-97c1-b467673f32e9\",\n            \"name\": \"office\",\n            \"value\": {\n                \"city\": \"London\",\n                \"country\": \"GBR\"\n            },\n            \"type\": \"complex\",\n            \"complexType\": {\n                \"type\": \"complex\",\n                \"multiValued\": false,\n                \"subAttributes\": [\n                    {\n                        \"name\": \"city\",\n                        \"type\": \"text\"\n                    },\n                    {\n                        \"name\": \"country\",\n                        \"type\": \"text\"\n                    }\n                ]\n            },\n            \"catalogName\": \"hr\"\n        },\n        {\n            \"id\": \"8d942b94-e72c-5128-96a5-8777ee923dc2\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"userId\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n            \"attributeId\": \"913031ce-7fc9-5ada-aca3-5b03d7a31e4f\",\n            \"name\": \"region\",\n            \"value\": \"EMEA\",\n            \"type\": \"text\",\n            \"catalogName\": \"hr\"\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 3,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 3,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"3a01f092-37aa-492a-bd3f-812fb3495990"},{"name":"Get User Attribute","id":"2b8894b9-d0b3-44a1-b7ce-dd112a541b68","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/attributes/{{user_attribute_value_id}}","description":"<p>Returns one attribute value of a user.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): The user ID.</li>\n<li><strong><code>user_attribute_value_id</code></strong> (string): The value <code>id</code> from <strong>Get User Attributes</strong> (not the attribute definition ID).</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","attributes","{{user_attribute_value_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"1f05e419-3b51-4272-b911-0badb9f70ff8","name":"Get User Attribute - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/attributes/{{user_attribute_value_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"9040dc22-33be-535c-985c-f13298938522\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"userId\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n    \"attributeId\": \"cc75fd66-ccfe-5bb1-8096-d32c6f78785d\",\n    \"name\": \"region\",\n    \"value\": \"EMEA\",\n    \"type\": \"text\"\n}"}],"_postman_id":"2b8894b9-d0b3-44a1-b7ce-dd112a541b68"},{"name":"Delete User Attribute","id":"d37b7f83-e035-45ad-8c1f-195b80d5faba","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/attributes/{{user_attribute_value_id}}","description":"<p>Removes one attribute value from a user.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): The user ID.</li>\n<li><strong><code>user_attribute_value_id</code></strong> (string): The value <code>id</code> from <strong>Get User Attributes</strong>.</li>\n</ul>\n<p>💡 Values that come from your identity provider can not be deleted here.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","attributes","{{user_attribute_value_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"0a5de39d-a030-4bf2-881a-6a2d9059b57e","name":"Delete User Attribute - 200 OK","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/attributes/{{user_attribute_value_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"d37b7f83-e035-45ad-8c1f-195b80d5faba"},{"name":"Set Group Attributes","id":"0ae22c5c-1dfe-4dbf-ba16-460b21a0399f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"attributesToUpsert\": [\n        {\n            \"attributeId\": \"{{attribute_id}}\",\n            \"value\": \"EMEA\"\n        }\n    ],\n    \"attributesToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/attributes","description":"<p>Sets or removes attribute values for a group. All users in the group get these values, unless the user has their own value.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>group_id</code></strong> (string): The group ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>attributesToUpsert</code></strong> (array): Values to set. Each item has:<ul>\n<li><strong><code>attributeId</code></strong> (string): The attribute definition ID (from <strong>Define Attributes</strong>).</li>\n<li><strong><code>value</code></strong>: The value, in the format of the attribute type (see <strong>Define Attributes</strong>). For example <code>\"EMEA\"</code>, <code>10000</code>, <code>[\"London\", \"Paris\"]</code> or <code>{\"city\": \"London\", \"country\": \"GBR\"}</code>.</li>\n</ul>\n</li>\n<li><strong><code>attributesToRemove</code></strong> (array of strings): Attribute definition IDs whose value you want to remove.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}","attributes"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"86a5a1d7-eef0-4676-93c7-9fdad1213ee4","name":"Set Group Attributes - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"attributesToUpsert\": [\n        {\n            \"attributeId\": \"{{attribute_id}}\",\n            \"value\": \"EMEA\"\n        }\n    ],\n    \"attributesToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/attributes"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"0ae22c5c-1dfe-4dbf-ba16-460b21a0399f"},{"name":"Get Group Attributes","id":"42b9de8e-3c58-4022-ab94-fa5993a5c321","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/attributes","description":"<p>Lists the attribute values of a group. The format is the same as <strong>Get User Attributes</strong>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>group_id</code></strong> (string): The group ID.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}","attributes"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"172f2be6-d5a6-4929-946f-cbd343abd354","name":"Get Group Attributes - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/attributes"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"attributes\": [\n        {\n            \"id\": \"d926e400-9734-58de-b579-a91f7b986922\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"groupId\": \"98101a7c-c6d6-54c0-9421-5e106a29e638\",\n            \"attributeId\": \"a7e38105-51f2-5996-bfa4-1902122303fe\",\n            \"name\": \"max_amount\",\n            \"value\": 10000,\n            \"type\": \"numeric\",\n            \"catalogName\": \"hr\"\n        },\n        {\n            \"id\": \"29816018-df13-5b4c-ad5f-dec06091c891\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"groupId\": \"98101a7c-c6d6-54c0-9421-5e106a29e638\",\n            \"attributeId\": \"913031ce-7fc9-5ada-aca3-5b03d7a31e4f\",\n            \"name\": \"region\",\n            \"value\": \"EMEA\",\n            \"type\": \"text\",\n            \"catalogName\": \"hr\"\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 2,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 2,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"42b9de8e-3c58-4022-ab94-fa5993a5c321"},{"name":"Get Group Attribute","id":"e55836cf-ddbe-428c-af35-168a06f26b3e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/attributes/{{group_attribute_value_id}}","description":"<p>Returns one attribute value of a group.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>group_id</code></strong> (string): The group ID.</li>\n<li><strong><code>group_attribute_value_id</code></strong> (string): The value <code>id</code> from <strong>Get Group Attributes</strong>.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}","attributes","{{group_attribute_value_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"23360e0d-27d3-47e4-a5a9-485dd2bff38f","name":"Get Group Attribute - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/attributes/{{group_attribute_value_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"3f4b6560-8d76-569a-a216-f441fde31b62\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"groupId\": \"aace47a2-a865-58e6-97a5-565c43a3612e\",\n    \"attributeId\": \"cc75fd66-ccfe-5bb1-8096-d32c6f78785d\",\n    \"name\": \"region\",\n    \"value\": \"EMEA\",\n    \"type\": \"text\",\n    \"catalogName\": \"hr\"\n}"}],"_postman_id":"e55836cf-ddbe-428c-af35-168a06f26b3e"},{"name":"Delete Group Attribute","id":"fa5daf83-05f1-427b-8775-5fd10079c870","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/attributes/{{group_attribute_value_id}}","description":"<p>Removes one attribute value from a group.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>group_id</code></strong> (string): The group ID.</li>\n<li><strong><code>group_attribute_value_id</code></strong> (string): The value <code>id</code> from <strong>Get Group Attributes</strong>.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}","attributes","{{group_attribute_value_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"75b3b16b-4a42-419f-b300-be8d83ba7067","name":"Delete Group Attribute - 200 OK","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/attributes/{{group_attribute_value_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"fa5daf83-05f1-427b-8775-5fd10079c870"}],"id":"973d5edf-9e97-48f6-b5e6-1ef8220d617e","description":"<p>Attributes describe your users, for example their region, team or branch. Row policies use them to show each user only their own rows (see <strong>Guide: Row-level security (RLS)</strong> in the Policies section).</p>\n<h3 id=\"how-attributes-are-organized\">How attributes are organized</h3>\n<ol>\n<li><strong>Catalog</strong>: a named group of attributes, for example <code>hr</code>.</li>\n<li><strong>Definition</strong>: an attribute inside a catalog, with a type, for example <code>region</code> (text).</li>\n<li><strong>Value</strong>: the value for one user or one group, for example <code>region = \"EMEA\"</code> for Dana.</li>\n</ol>\n<p>In a row policy you write the full name in double curly braces: <code>{{hr.region}}</code>.</p>\n<p>[SCREENSHOT: attributes_catalog_definitions.png]</p>\n<p>[SCREENSHOT: attributes_user_values.png]</p>\n<h3 id=\"where-values-come-from\">Where values come from</h3>\n<ul>\n<li><strong>Set them with the API</strong> (this section) or in the PVML UI, per user or per group.</li>\n<li><strong>From your identity provider</strong>: link the catalog to an SSO provider and set up SCIM. PVML updates the values each time your identity provider sends a change. See <strong>Workspaces &gt; Guide: SSO and SCIM setup</strong>.</li>\n</ul>\n<h3 id=\"which-value-wins\">Which value wins</h3>\n<ol>\n<li>A value set on the user.</li>\n<li>Otherwise, the value set on the user's group.</li>\n<li>Otherwise, the user has no value. Any query that needs it is blocked. This is secure by default: no value means no data.</li>\n</ol>\n","_postman_id":"973d5edf-9e97-48f6-b5e6-1ef8220d617e","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Invite Users","id":"8b6c50e1-30fd-488f-84bc-53f2926783c3","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"usersToInvite\": [\n        \"someone@example.com\"\n    ],\n    \"userRole\": \"member\",\n    \"groupsToAdd\": [\n        \"{{group_id}}\"\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/invite","description":"<p>This endpoint allows you to invite users to a workspace by email.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><code>workspace_id</code>(string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<ul>\n<li><code>usersToInvite</code> (array): The email addresses to invite.</li>\n<li><code>userRole</code> (string): The role of the new users: <code>member</code>, <code>admin</code> or <code>owner</code>.</li>\n<li><code>groupsToAdd</code> (array, optional): Group IDs to add the new users to.</li>\n</ul>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p>Upon successful invite of the users, the server returns a status code of 200 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","invite"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"65f7ddf4-0e1a-4cf6-93fc-e1626e01ec88","name":"Invite Users - 200 Success","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"usersToInvite\": [\n        \"someone@example.com\"\n    ],\n    \"userRole\": \"member\",\n    \"groupsToAdd\": [\n        \"{{group_id}}\"\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/invite"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\"AlreadyExistsInWorkspace\":[],\"NotExistsInWorkspace\":[\"someone@example.com\"]}"}],"_postman_id":"8b6c50e1-30fd-488f-84bc-53f2926783c3"},{"name":"Get Pending Invitations","id":"3c35c0e0-65c4-40bd-82e4-4bdc9c2266a5","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/invitations","description":"<p>Lists invitations that were sent and not used yet. Expired invitations are not listed.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>invitations</code></strong> (array): <code>target_user_email</code>, <code>invited_by</code>, <code>workspace_id</code>, <code>expiration_date</code>, <code>created_at</code>. <code>null</code> when there are none.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","invitations"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"38693122-56d8-4824-9488-49732a3a7aad","name":"Get Pending Invitations - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/invitations"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"invitations\": [\n        {\n            \"target_user_email\": \"someone@example.com\",\n            \"invited_by\": \"jane.doe@example.com\",\n            \"workspace_id\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n            \"expiration_date\": \"2026-11-06T10:00:00Z\",\n            \"created_at\": \"2026-10-30T10:00:00Z\"\n        }\n    ]\n}"}],"_postman_id":"3c35c0e0-65c4-40bd-82e4-4bdc9c2266a5"},{"name":"Revoke Invitation","id":"4a9e6b21-436c-4fd1-9352-16393a4456a8","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"body":{"mode":"raw","raw":"{\n    \"target_user_email\": \"someone@example.com\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/invitations","description":"<p>Cancels the invitation of one email address, so it can no longer be used. PVML does not send an email about it.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>target_user_email</code></strong> (string): The invited email address.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","invitations"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"a1d793e1-b182-4990-805f-93d3eb2df645","name":"Revoke Invitation - 200 OK","originalRequest":{"method":"DELETE","header":[],"body":{"mode":"raw","raw":"{\n    \"target_user_email\": \"someone@example.com\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/invitations"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"4a9e6b21-436c-4fd1-9352-16393a4456a8"},{"name":"Verify Users in Workspace","id":"36493177-c8c3-4871-be6e-456f9924782f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"usersOfWorkspace\": [\"{{user_id}}\"]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/exists","description":"<p>This endpoint allows you to check if users belong to a workspace using their ids.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><code>workspace_id</code>(string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<ul>\n<li><code>usersOfWorkspace</code>: An array containing the user IDs to check whether they belong to the workspace.</li>\n</ul>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<ul>\n<li><code>isPartOfWorkspace</code> (boolean): indicating whether <strong>all the specified users</strong> belong to the workspace.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","exists"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"e732fda8-46c6-402b-b19b-d350e43c5349","name":"Verify Users in Workspace - 200 Success","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"usersOfWorkspace\": [\"{{user_id}}\", \"51e46e48-96ab-54bf-a1bb-e81947323d8b\"]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/exists"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"isPartOfWorkspace\": true\n}"}],"_postman_id":"36493177-c8c3-4871-be6e-456f9924782f"},{"name":"Create Service Account","id":"c7ccc83c-bf2f-40eb-8bd0-eea07a207da4","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"service_account_username\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/adhoc","description":"<p>Creates a service account in a workspace. In the platform: <strong>Users &gt; Create service account</strong>.</p>\n<p>A service account is for an app, a script or a BI tool. It can not log in to the platform. Give it access to VDBs like any user, then create a REST API key or an ODBC/JDBC connection string for it (see <strong>Connections</strong>).</p>\n<p>💡 In the API, a service account is called <code>adhoc</code>: this endpoint is <code>/users/adhoc</code>, and the user has <code>type: \"adhoc\"</code>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><code>workspace_id</code> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><code>name</code> (string): The name of the service account.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><code>id</code> (string): The unique identifier of the service account.</li>\n<li><code>email</code> (string): <code>NAME@WORKSPACE_ID.pvml.com</code>. This is not a real email address.</li>\n<li><code>createdAt</code> (string): When the service account was created (ISO 8601).</li>\n<li><code>image</code> (string): The path to the user's image.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","adhoc"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"fa861f57-10bd-4aa8-a8ad-607b05a00188","name":"Create Service Account - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"service_account_username\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/adhoc"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"User\": {\n        \"id\": \"23d8fc1c-f54a-52ed-a1fd-9c2928f37f2b\",\n        \"email\": \"reporting_service@5a080071-4995-52ef-8dce-9b5ad367a3d4.pvml.com\",\n        \"created_at\": \"2026-10-29T23:55:56.60377Z\",\n        \"image\": \"\"\n    },\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"joined_at\": \"2026-10-29T23:55:56.605696Z\",\n    \"role\": \"member\",\n    \"type\": \"adhoc\"\n}"}],"_postman_id":"c7ccc83c-bf2f-40eb-8bd0-eea07a207da4"},{"name":"Get User","id":"c7248b4f-8400-41cf-8a1d-80e489cd350c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}?enrich=true","description":"<p>This endpoint retrieves the user associated with a specific workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>user_id</code> (string): The unique identifier of the user.</p>\n</li>\n<li><p><code>enrich</code> (boolean, query parameter): when set to <code>true</code>, the request returns additional details such as the user's image.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><p><code>User</code>:</p>\n<ul>\n<li><p><code>id</code> <em>(String):</em> Unique identifier for the user.</p>\n</li>\n<li><p><code>email</code> <em>(String):</em> Email address of the user.</p>\n</li>\n<li><p><code>created_at</code> <em>(String, ISO 8601 format):</em> Timestamp when the user was created/joined.</p>\n</li>\n<li><p><code>image</code> <em>(String, URL):</em> Link to the user’s profile picture or avatar.</p>\n</li>\n</ul>\n</li>\n<li><p><code>workspaceId</code> (<em>String)</em>: Unique identifier for the workspace to which the user belongs.</p>\n</li>\n<li><p><strong>joined_at</strong>: <em>(String, ISO 8601 format)</em> Timestamp when the user joined the workspace.</p>\n</li>\n<li><p><strong>role</strong>: <em>(String)</em> Role of the user in the workspace: \"owner,\" \"admin,\" or \"member.\"</p>\n</li>\n<li><p><strong>type</strong>: <em>(String)</em> The type of the user in the workspace: <code>regular</code> (platform user) or <code>adhoc</code> (service account).</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}"],"host":["platform","pvml","com"],"query":[{"key":"enrich","value":"true"}],"variable":[]}},"response":[{"id":"ef203e63-3423-4131-b325-98acbdc0d5c0","name":"Get User - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}?enrich=true","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}"],"query":[{"key":"enrich","value":"true"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"User\": {\n        \"id\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n        \"email\": \"jane.doe@example.com\",\n        \"created_at\": \"2023-10-30T10:18:59.843367Z\",\n        \"image\": \"\"\n    },\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"joined_at\": \"2026-10-29T23:26:19.455862Z\",\n    \"role\": \"owner\",\n    \"type\": \"regular\"\n}"}],"_postman_id":"c7248b4f-8400-41cf-8a1d-80e489cd350c"},{"name":"Get Users","id":"c4946a53-1fa2-48e5-8695-b03ebfca80d1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users?enrich=true","description":"<p>This endpoint retrieves the users associated with a specific workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>enrich</code> (boolean, query parameter): when set to <code>true</code>, the request returns additional details such as the user's image.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><p><code>workspaceUsers</code>: a list of users associated with a workspace</p>\n<ul>\n<li><p><code>User</code>:</p>\n<ul>\n<li><p><code>id</code> (string): Unique identifier for the user.</p>\n</li>\n<li><p><code>email</code> (string): Email address of the user.</p>\n</li>\n<li><p><code>created_at</code> (string, ISO 8601 format): Timestamp when the user was created/joined.</p>\n</li>\n<li><p><code>image</code> (string, URL): Link to the user’s profile picture or avatar.</p>\n</li>\n</ul>\n</li>\n<li><p><code>workspaceId</code> (string): Unique identifier for the workspace to which the user belongs.</p>\n</li>\n<li><p><code>joined_at</code> (string, ISO 8601 format): Timestamp when the user joined the workspace.</p>\n</li>\n<li><p><code>role</code> (string): Role of the user in the workspace: \"owner,\" \"admin,\" or \"member.\"</p>\n</li>\n<li><p><code>type</code> (string): The type of the user in the workspace: <code>regular</code> (platform user) or <code>adhoc</code> (service account).</p>\n</li>\n</ul>\n</li>\n</ul>\n<p>💡 The response also has a <strong><code>pagination</code></strong> object: <code>totalRecords</code>, <code>currentPage</code>, <code>totalPages</code>, <code>pageSize</code>, <code>nextPage</code>, <code>prevPage</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users"],"host":["platform","pvml","com"],"query":[{"key":"enrich","value":"true"}],"variable":[]}},"response":[{"id":"55d16386-0e8f-42b1-9cd4-3a80bb1cc933","name":"Get Users - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users?enrich=true","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","users"],"query":[{"key":"enrich","value":"true"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"workspaceUsers\": [\n        {\n            \"User\": {\n                \"id\": \"23d8fc1c-f54a-52ed-a1fd-9c2928f37f2b\",\n                \"email\": \"reporting_service@5a080071-4995-52ef-8dce-9b5ad367a3d4.pvml.com\",\n                \"created_at\": \"2026-10-29T23:55:56.605696Z\",\n                \"image\": \"\"\n            },\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"joined_at\": \"0001-01-01T00:00:00Z\",\n            \"role\": \"member\",\n            \"type\": \"adhoc\"\n        },\n        {\n            \"User\": {\n                \"id\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n                \"email\": \"jane.doe@example.com\",\n                \"created_at\": \"2026-10-29T23:26:19.455862Z\",\n                \"image\": \"\"\n            },\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"joined_at\": \"0001-01-01T00:00:00Z\",\n            \"role\": \"owner\",\n            \"type\": \"regular\"\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 2,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 2,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"c4946a53-1fa2-48e5-8695-b03ebfca80d1"},{"name":"Get User VDBs","id":"2352d0dd-4712-4f1a-a60b-bc11ced9fe90","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/vdbs","description":"<p>Lists the VDBs a user can access, directly or through groups.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): The user ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>vdbs</code></strong> (array): VDB objects (<code>vdbId</code>, <code>name</code>, <code>description</code>, <code>datasourceId</code>, <code>datasourceType</code>, <code>createdAt</code>, ...).</li>\n<li><strong><code>pagination</code></strong> (object).</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","vdbs"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"18aa208c-2881-4f8b-ac35-b4ba6db8fb83","name":"Get User VDBs - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/vdbs"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"vdbs\": [\n        {\n            \"vdbId\": \"df8fe2c3-7c75-5f3e-8d47-d08181298485\",\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceType\": \"BigQuery\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"name\": \"clinic analytics\",\n            \"description\": \"VDB for the clinic analytics team\",\n            \"shouldEnforce\": false,\n            \"permissionsLastModifiedAt\": 1793318159168,\n            \"createdAt\": 1793318158530,\n            \"lastModifiedAt\": 1793318159171\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 1,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 1,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"2352d0dd-4712-4f1a-a60b-bc11ced9fe90"},{"name":"Get User Groups","id":"e3645a7d-3a3d-42ab-81a7-77e44360d178","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/groups","description":"<p>Lists the groups of a user.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): The user ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>groups</code></strong> (array): <code>id</code>, <code>name</code>, <code>description</code>, <code>workspaceId</code>, <code>createdAt</code>, <code>lastModifiedAt</code>.</li>\n<li><strong><code>pagination</code></strong> (object).</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","groups"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"7acc952d-e5ce-48ae-b45c-c3145e678d76","name":"Get User Groups - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/groups"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"groups\": [\n        {\n            \"id\": \"d8c07ec8-f3e6-5482-99c3-e708d18bebab\",\n            \"name\": \"analysts\",\n            \"description\": \"Data analysts team\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"createdAt\": \"2026-10-29T23:55:56.252183Z\",\n            \"lastModifiedAt\": \"2026-10-29T23:55:56.430034Z\",\n            \"userAttachedAt\": \"2026-10-29T23:55:56.42756Z\"\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 1,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 1,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"e3645a7d-3a3d-42ab-81a7-77e44360d178"},{"name":"Get User Role","id":"c1e99a95-f6f7-46dc-b3a8-5fdf15cd0cdd","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/role","description":"<p>This endpoint retrieves the role associated with a specific user in a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>user_id</code>(string): the unique id of the user in the workspace</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><code>userRole</code> (string): The user's role in the workspace (<em>member</em> / <em>admin</em> / <em>owner</em>)</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","role"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"58b4ca66-ba19-4992-947f-2a8c574efb41","name":"Get User Role - 200 Success","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/role"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"userRole\": \"owner\"\n}"}],"_postman_id":"c1e99a95-f6f7-46dc-b3a8-5fdf15cd0cdd"},{"name":"Update User Groups","id":"b956b541-3a22-4a45-8c80-d3b247caa08e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"groupsToAdd\": [\n        \"{{group_id}}\"\n    ],\n    \"groupsToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/groups","description":"<p>Adds a user to groups or removes them from groups.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): The user ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>groupsToAdd</code></strong> (array of strings): Group IDs.</li>\n<li><strong><code>groupsToRemove</code></strong> (array of strings): Group IDs.</li>\n</ul>\n<p>At least one list must have an item.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 201 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","groups"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"938c38f6-a045-4638-99e7-ec5698097db8","name":"Update User Groups - 201 Created","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"groupsToAdd\": [\n        \"{{group_id}}\"\n    ],\n    \"groupsToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/groups"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"b956b541-3a22-4a45-8c80-d3b247caa08e"},{"name":"Update User VDBs","id":"87e10cb9-7405-4332-b7df-ce70306bca9a","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"vdbsToAdd\": [\n        \"{{vdb_id}}\"\n    ],\n    \"vdbsToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/vdbs","description":"<p>Gives a user direct access to VDBs, or removes it.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): The user ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>vdbsToAdd</code></strong> (array of strings, required): VDB IDs.</li>\n<li><strong><code>vdbsToRemove</code></strong> (array of strings, required): VDB IDs.</li>\n</ul>\n<p>Send both keys. Use <code>[]</code> for an empty list.</p>\n<p>💡 This is the same as <strong>Update Virtual DB Users</strong>, seen from the user side. A user also gets access to the VDBs of their groups.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","vdbs"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"7c37bb31-f0d2-4f5b-8678-0ebf06656ad7","name":"Update User VDBs - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"vdbsToAdd\": [\n        \"{{vdb_id}}\"\n    ],\n    \"vdbsToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/vdbs"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"87e10cb9-7405-4332-b7df-ce70306bca9a"},{"name":"Update User Role","id":"590f776c-41b7-440a-9f77-6c843d33114e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n \"role\":\"member\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/role","description":"<p>This endpoint allows to change the role of a specific user in a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>user_id</code>(string): the unique id of the user in the workspace</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><code>role</code> (string): The user's new role in the workspace: <code>member</code>, <code>admin</code> or <code>owner</code>.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Upon successful update, the server returns a status code of 201 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","role"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"3bbd63ce-9719-4268-8012-11d0ac0b841f","name":"Update User Role","originalRequest":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n \"role\":\"member\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/role"},"status":"Created","code":201,"_postman_previewlanguage":"Text","header":[],"cookie":[{"expires":"Invalid Date","domain":"","path":""}],"responseTime":null,"body":""}],"_postman_id":"590f776c-41b7-440a-9f77-6c843d33114e"},{"name":"Leave Workspace","id":"8a7ddbdc-0c4f-488a-a2fc-bd2a8409cf9e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}","description":"<p>Removes one user from a workspace. Use your own user ID to leave the workspace. Admins can also use it to remove a member. To remove several users at once, use <strong>Remove Users from Workspace</strong>. The workspace itself is not deleted.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code> (string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>user_id</code>(string): the unique id of the user in the workspace</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Upon successful deletion of the user from the workspace, the server returns a status code of 200 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"0a96b5a3-2da4-45b4-800b-c1c4f2b9f995","name":"Delete User From Workspace - 200 Success","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}"},"_postman_previewlanguage":"Text","header":[],"cookie":[],"responseTime":null,"body":"\"\""}],"_postman_id":"8a7ddbdc-0c4f-488a-a2fc-bd2a8409cf9e"},{"name":"Remove Users from Workspace","id":"be6e3cab-cfa3-42cf-8595-2bec8dab6237","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"body":{"mode":"raw","raw":"{\n    \"usersToRemove\": [\n        \"{{user_id}}\"\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users","description":"<p>Removes one or more users from the workspace.</p>\n<p>In the platform: <strong>Users</strong>, then <strong>Remove user</strong> in the user's menu.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>usersToRemove</code></strong> (array of strings): The user IDs to remove.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<h3 id=\"good-to-know\">Good to know</h3>\n<ul>\n<li>Regular users keep their PVML account and their other workspaces. They lose all access in this workspace.</li>\n<li>Service accounts are deleted.</li>\n<li>You can not remove yourself. To leave a workspace, use <strong>Leave Workspace</strong>.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"b9f7d64c-68f4-4b3b-9ff1-759bc23a1945","name":"Remove Users from Workspace - 200","originalRequest":{"method":"DELETE","header":[],"body":{"mode":"raw","raw":"{\n    \"usersToRemove\": [\n        \"{{user_id}}\"\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"be6e3cab-cfa3-42cf-8595-2bec8dab6237"}],"id":"525391b6-eb75-4d9d-af69-0ba429ca67e6","description":"<p>PVML has two kinds of users:</p>\n<ul>\n<li><strong>Users</strong>: people invited by email. They log in to the platform and can also use the API.</li>\n<li><strong>Service accounts</strong>: for apps, scripts and BI tools. They can not log in to the platform. They connect with a REST API key or an ODBC/JDBC connection string (see <strong>Connections</strong>). In the platform: <strong>Create service account</strong>.</li>\n</ul>\n<p>💡 <strong>Names in the API:</strong> a user has <code>type: \"regular\"</code>, a service account has <code>type: \"adhoc\"</code>. The endpoint to create a service account is <code>/users/adhoc</code>.</p>\n<img src=\"https://content.pstmn.io/193e63d9-a7c7-48a0-84e2-b0c100fcc3a5/QVBJIGltYWdlcy5wbmc=\" alt=\"Invite%20users%20to%20the%20workspace%20or%20create%20service%20accounts.\" />\n\n<p>Users and service accounts can be managed one by one or in groups.</p>\n<p>💡 <strong>What does a service account look like?</strong></p>\n<p>When you create a service account, the name you give is converted to an \"email\" with the following format: <a href=\"https://mailto:NAME@WORKSPACE_ID.pvml.com\">NAME@WORKSPACE_ID.pvml.com</a> (note: this is not a real email, it cannot be used to log into the platform). The user is assigned an id just like a regular user.</p>\n<p>💡 <strong>How can I get my user id?</strong></p>\n<p>To get a user ID, open the <strong>Users</strong> page (under <strong>Data Access</strong>) and copy the user ID.</p>\n<h3 id=\"what-can-each-user-role-do\">What Can Each User Role Do?</h3>\n<ul>\n<li><p><strong>Member</strong>:</p>\n<ul>\n<li><p>Get a connection string from a VDB they have access to.</p>\n</li>\n<li><p>Access the <em>Notebook</em> app on the PVML platform.</p>\n</li>\n<li><p>Request access. Admins see the requests under the bell icon at the top right of the platform. See <strong>Access Requests</strong>.</p>\n</li>\n</ul>\n</li>\n<li><p><strong>Admin</strong>: Everything members can do, plus:</p>\n<ul>\n<li><p>Fully configure resources such as datasources, policies, VDBs and LLMs.</p>\n</li>\n<li><p>Invite/create users.</p>\n</li>\n<li><p>Approve or decline access requests (the bell icon at the top right). This also works with the API, so you can build your own approval flow, for example a Slack bot.</p>\n</li>\n</ul>\n</li>\n<li><p><strong>Owner</strong>: Everything admins can do, plus:</p>\n<ul>\n<li>Deleting a workspace.</li>\n</ul>\n</li>\n</ul>\n","_postman_id":"525391b6-eb75-4d9d-af69-0ba429ca67e6","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Access Requests","item":[{"name":"Tutorial: Build access requests in Slack","item":[],"id":"3aceca6a-7051-4c07-801d-442fea5a8922","description":"<p>Build a Slack app that lets people ask for data access in Slack, and lets data owners approve it with one click. By the end, you have a working app that you own and can adapt to your company's process.</p>\n<p><strong>Time:</strong> about 30 minutes. <strong>Level:</strong> you are comfortable with Python and Slack app settings.</p>\n<h3 id=\"what-you-will-build\">What you will build</h3>\n<p>A Slack app called <strong>PVML Access</strong> with these commands:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Command</th>\n<th>What it does</th>\n<th>PVML API used</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>/access</code></td>\n<td>Lists the groups people can ask for.</td>\n<td><strong>Get Groups</strong></td>\n</tr>\n<tr>\n<td><code>/access &lt;group&gt;</code></td>\n<td>Asks to join a group. Data owners approve or reject it in a channel. After an approval, the user gets the MCP servers of the group's VDBs.</td>\n<td><strong>Request to Join a Group</strong>, <strong>Approve or Reject Request</strong>, <strong>Get Group VDBs</strong>, <strong>Get MCPs</strong></td>\n</tr>\n<tr>\n<td><code>/access-like @teammate</code></td>\n<td>Asks for the same groups as a teammate. Good for a new team member. Data owners see the groups and approve or reject.</td>\n<td><strong>Get User Groups</strong>, <strong>Request Same Groups as Another User</strong>, <strong>Approve or Reject Request</strong>, <strong>Get User VDBs</strong>, <strong>Get MCPs</strong></td>\n</tr>\n<tr>\n<td><code>/my-vdbs</code></td>\n<td>Lists the user's VDBs and their MCP servers.</td>\n<td><strong>Get User VDBs</strong>, <strong>Get MCPs</strong></td>\n</tr>\n<tr>\n<td><code>/my-groups</code></td>\n<td>Lists the user's groups.</td>\n<td><strong>Get User Groups</strong></td>\n</tr>\n<tr>\n<td><code>/connection &lt;VDB&gt; [days]</code></td>\n<td>Asks for an ODBC/JDBC connection string. The data owner picks how long it is valid. The user sees it once, in a private pop-up.</td>\n<td><strong>Create ODBC/JDBC Connection String</strong></td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Example:</strong> Dana, a new analyst, types <code>/access finance analysts</code>. Noa, a data owner, clicks <strong>Approve</strong> in the owners' channel. PVML adds Dana to the group, and Dana gets the MCP server of the finance VDB in a direct message. A minute later she asks her first question in her AI client. Every answer follows the VDB's policies.</p>\n<h3 id=\"before-you-begin\">Before you begin</h3>\n<p>You need:</p>\n<ul>\n<li>A PVML workspace where you are an <strong>admin</strong>.</li>\n<li>A Slack workspace where you can <strong>create and install apps</strong>.</li>\n<li><strong>Python 3.9</strong> or newer.</li>\n<li>For production: a place to run a small, always-on service, for example a VM with Docker or a Kubernetes cluster. The app is light (about 0.1 CPU and 256 MB of memory), so the smallest VM is enough, often a free-tier one. It needs no public URL.</li>\n</ul>\n<p>People use the app with their Slack email. They must be users in the PVML workspace with the same email (invite them, or sync them with SCIM: see <strong>Workspaces &gt; Guide: SSO and SCIM setup</strong>).</p>\n<h3 id=\"step-1-prepare-pvml\">Step 1: Prepare PVML</h3>\n<ol>\n<li>Create a service account for the app, for example <code>slack-access-bot</code> (<strong>Create Service Account</strong>). Copy its user ID.</li>\n<li>Give it the <code>admin</code> role (<strong>Update User Role</strong>). Only admins can approve requests.</li>\n<li>Create a REST API key for it (<strong>Create User API Key</strong>). Set a long expiry, for example 1 year. Copy the key and its <code>tokenId</code>.</li>\n<li>Check that each team has a group, and that the group has the right VDBs (<strong>Update Group VDBs</strong>). People ask for groups, not for single VDBs.</li>\n</ol>\n<p>⚠️ The app acts with this account's admin rights. Keep the key in a secret manager, and choose your approvers carefully (Step 3).</p>\n<h3 id=\"step-2-create-the-slack-app\">Step 2: Create the Slack app</h3>\n<ol>\n<li>Go to api.slack.com/apps and click <strong>Create New App &gt; From an app manifest</strong>. Pick your workspace and paste:</li>\n</ol>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-yaml\">display_information:\n  name: PVML Access\n  description: Ask for PVML data access and approve it in Slack\nfeatures:\n  bot_user:\n    display_name: PVML Access\n    always_online: false\n  slash_commands:\n    - command: /access\n      description: Ask for access to a PVML group\n      usage_hint: \"[group name], or leave empty to see the groups\"\n      should_escape: false\n    - command: /access-like\n      description: Ask for the same PVML groups as a teammate\n      usage_hint: \"@teammate\"\n      should_escape: true\n    - command: /connection\n      description: Ask for an ODBC/JDBC connection to one of your VDBs\n      usage_hint: \"[VDB name] [days, optional]\"\n      should_escape: false\n    - command: /my-vdbs\n      description: Your PVML VDBs and their MCP servers\n      should_escape: false\n    - command: /my-groups\n      description: Your PVML groups\n      should_escape: false\noauth_config:\n  scopes:\n    bot:\n      - commands\n      - chat:write\n      - users:read\n      - users:read.email\nsettings:\n  interactivity:\n    is_enabled: true\n  org_deploy_enabled: false\n  socket_mode_enabled: true\n  token_rotation_enabled: false\n</code></pre>\n<ol>\n<li><strong>Basic Information &gt; App-Level Tokens:</strong> generate a token with the <code>connections:write</code> scope. Copy it (<code>xapp-...</code>).</li>\n<li><strong>Install App:</strong> install the app to your workspace. Copy the <strong>Bot User OAuth Token</strong> (<code>xoxb-...</code>).</li>\n<li>Invite the app to the data owners' channel: <code>/invite @PVML Access</code>.</li>\n</ol>\n<p>The manifest turns on Socket Mode, so the app connects out to Slack and needs no public URL or open ports.</p>\n<h3 id=\"step-3-add-the-code-and-settings\">Step 3: Add the code and settings</h3>\n<ol>\n<li>Install the libraries:</li>\n</ol>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>pip install slack_bolt requests\n</code></pre><ol>\n<li>Set these environment variables:</li>\n</ol>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Variable</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>SLACK_BOT_TOKEN</code></td>\n<td>The bot token (<code>xoxb-...</code>)</td>\n</tr>\n<tr>\n<td><code>SLACK_APP_TOKEN</code></td>\n<td>The app-level token (<code>xapp-...</code>)</td>\n</tr>\n<tr>\n<td><code>SLACK_APPROVAL_CHANNEL</code></td>\n<td>The channel ID of the data owners</td>\n</tr>\n<tr>\n<td><code>SLACK_APPROVERS</code></td>\n<td>Slack member IDs of the people who may approve, comma-separated</td>\n</tr>\n<tr>\n<td><code>PVML_BASE_URL</code></td>\n<td>Optional. Your PVML address, for self-hosted PVML (default <code>https://platform.pvml.com</code>)</td>\n</tr>\n<tr>\n<td><code>PVML_WORKSPACE_ID</code></td>\n<td>Your workspace ID</td>\n</tr>\n<tr>\n<td><code>PVML_API_KEY</code></td>\n<td>The API key from Step 1</td>\n</tr>\n<tr>\n<td><code>PVML_BOT_USER_ID</code></td>\n<td>The user ID of the service account from Step 1</td>\n</tr>\n<tr>\n<td><code>PVML_API_KEY_ID</code></td>\n<td>Optional. The key's <code>tokenId</code>. The app then warns the owners 7 days before the key expires.</td>\n</tr>\n<tr>\n<td><code>CONN_DEFAULT_DAYS</code></td>\n<td>Optional. Connection string expiry when nobody picks one (default <code>30</code>)</td>\n</tr>\n<tr>\n<td><code>CONN_MAX_DAYS</code></td>\n<td>Optional. Longest connection string expiry (default <code>90</code>)</td>\n</tr>\n</tbody>\n</table>\n</div><ol>\n<li>Make a new folder for the app. Save this code in it as <code>bot.py</code>:</li>\n</ol>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-python\">import os, re, json, time, threading, requests\nfrom slack_bolt import App\nfrom slack_bolt.adapter.socket_mode import SocketModeHandler\nfrom slack_sdk.errors import SlackApiError\n\nPVML = os.environ.get(\"PVML_BASE_URL\", \"https://platform.pvml.com\") + \"/platform/api/v1\"\nWS = os.environ[\"PVML_WORKSPACE_ID\"]\nBOT_USER = os.environ[\"PVML_BOT_USER_ID\"]        # user ID of the bot's service account\nHEADERS = {\"Authorization\": \"Bearer \" + os.environ[\"PVML_API_KEY\"]}\nCHANNEL = os.environ[\"SLACK_APPROVAL_CHANNEL\"]   # channel of the data owners\nAPPROVERS = set(os.environ[\"SLACK_APPROVERS\"].split(\",\"))  # Slack user IDs who may approve\n\napp = App(token=os.environ[\"SLACK_BOT_TOKEN\"])\nDEFAULT_DAYS = int(os.environ.get(\"CONN_DEFAULT_DAYS\", \"30\"))   # connection string expiry if nobody picks one\nMAX_DAYS = int(os.environ.get(\"CONN_MAX_DAYS\", \"90\"))           # longest expiry anyone can get\nKEY_ID = os.environ.get(\"PVML_API_KEY_ID\", \"\")                  # ID of the bot's API key, to warn before it expires\nWARN_DAYS = 7\n\n\nclass KeyRejected(Exception):\n    pass\n\n\ndef pvml(method, path, **kw):\n    r = requests.request(method, f\"{PVML}/workspaces/{WS}{path}\", headers=HEADERS, timeout=30, **kw)\n    if r.status_code == 401:\n        raise KeyRejected()\n    r.raise_for_status()\n    return r.json() if r.text.strip() not in (\"\", \"null\") else None\n\n\ndef find_user(email):\n    users = pvml(\"GET\", \"/users\", params={\"searchText\": email})[\"workspaceUsers\"]\n    return next((u[\"User\"][\"id\"] for u in users if u[\"User\"][\"email\"].lower() == email.lower()), None)\n\n\ndef clean(text):\n    # Remove Slack formatting (*bold*, _italic_, ~strike~, `code`) and quotes that people copy with a name\n    return \" \".join(text.strip(\"*_~`'\\\" \").split())\n\n\ndef slack_user_to_pvml(client, slack_user):\n    email = client.users_info(user=slack_user)[\"user\"][\"profile\"].get(\"email\", \"\")\n    return email, (find_user(email) if email else None)\n\n\nMCP_HELP = (\"Add a URL as a custom connector (MCP server) in your AI client, for example Claude, \"\n            \"ChatGPT or Cursor, and sign in with your company account. The AI sees only what you can see.\")\n\n\ndef mcp_lines(vdbs):\n    mcps = {m[\"internalReferences\"][\"vdbId\"]: m[\"url\"] for m in (pvml(\"GET\", \"/mcps\")[\"mcps\"] or [])}\n    return [f\"- *{v['name']}*: {mcps[v['vdbId']]}\" for v in vdbs if v[\"vdbId\"] in mcps]\n\n\ndef ask_owners(client, respond, text, blocks):\n    # Post to the owners' channel. Returns False (and tells the user) if the bot can not post there.\n    try:\n        client.chat_postMessage(channel=CHANNEL, text=text, blocks=blocks)\n        return True\n    except SlackApiError as e:\n        print(f\"Can not post to {CHANNEL}: {e.response['error']} (is the bot invited to the channel?)\")\n        respond(\"Sorry, I could not reach the data owners. Please tell your PVML admin.\")\n        return False\n\n\ndef find_group(name):\n    groups = pvml(\"GET\", \"/groups\")[\"groups\"]\n    return next((g for g in groups if g[\"name\"].lower() == name.lower()), None), [g[\"name\"] for g in groups]\n\n\ndef decision_buttons(text, value):\n    return [{\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": text}},\n            {\"type\": \"actions\", \"elements\": [\n                {\"type\": \"button\", \"text\": {\"type\": \"plain_text\", \"text\": \"Approve\"}, \"style\": \"primary\",\n                 \"action_id\": \"approve\", \"value\": value},\n                {\"type\": \"button\", \"text\": {\"type\": \"plain_text\", \"text\": \"Reject\"}, \"style\": \"danger\",\n                 \"action_id\": \"reject\", \"value\": value}]}]\n\n\ndef done(client, channel, ts, text):\n    # Replace the buttons with the final text\n    client.chat_update(channel=channel, ts=ts, text=text,\n                       blocks=[{\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": text}}])\n\n\n@app.command(\"/access\")\ndef request_access(ack, command, client, respond):\n    ack()\n    group_name = clean(command[\"text\"])\n    if not group_name:  # \"/access\" alone lists the groups\n        groups = pvml(\"GET\", \"/groups\")[\"groups\"]\n        lines = [f\"- *{g['name']}*\" + (f\": {g['description']}\" if g.get(\"description\") else \"\") for g in groups]\n        return respond(\"Groups you can ask for:\\n\" + \"\\n\".join(lines) + \"\\nType `/access &lt;group name&gt;`, \"\n                       \"or `/access-like @teammate` to ask for the same groups as a teammate.\")\n    email, user_id = slack_user_to_pvml(client, command[\"user_id\"])\n    if not user_id:\n        return respond(f\"You are not a user in PVML yet ({email}). Ask an admin to invite you.\")\n    group, all_groups = find_group(group_name)\n    if not group:\n        return respond(f\"No group named '{group_name}'. Groups: {', '.join(all_groups)}\")\n    event = pvml(\"POST\", f\"/groups/{group['id']}/events/join\",\n                 json={\"userToAdd\": user_id, \"senderId\": BOT_USER})[\"Event\"]\n    text = f\"&lt;@{command['user_id']}&gt; asks to join *{group['name']}* in PVML.\"\n    value = json.dumps({\"event\": event[\"id\"], \"requester\": command[\"user_id\"], \"group\": group[\"id\"]})\n    if not ask_owners(client, respond, text, decision_buttons(text, value)):\n        return pvml(\"DELETE\", f\"/cancel/{event['id']}\")  # nobody can approve it, so cancel the PVML request\n    respond(f\"Your request to join *{group['name']}* was sent to the data owners.\")\n\n\n@app.command(\"/access-like\")\ndef request_same_access(ack, command, client, respond):\n    ack()\n    tag = re.search(r\"&lt;@(\\w+)(?:\\|[^&gt;]*)?&gt;\", command[\"text\"])  # Slack sends a tagged person as &lt;@U123|name&gt;\n    if not tag:\n        return respond(\"Type `/access-like @teammate` to ask for the same groups as a teammate.\")\n    teammate = tag.group(1)\n    if teammate == command[\"user_id\"]:\n        return respond(\"Tag a teammate, not yourself.\")\n    email, user_id = slack_user_to_pvml(client, command[\"user_id\"])\n    if not user_id:\n        return respond(f\"You are not a user in PVML yet ({email}). Ask an admin to invite you.\")\n    _, teammate_id = slack_user_to_pvml(client, teammate)\n    if not teammate_id:\n        return respond(f\"&lt;@{teammate}&gt; is not a user in PVML.\")\n    names = [g[\"name\"] for g in pvml(\"GET\", f\"/users/{teammate_id}/groups\")[\"groups\"] or []]\n    if not names:\n        return respond(f\"&lt;@{teammate}&gt; is not in any group yet, so there is nothing to ask for.\")\n    event = pvml(\"POST\", f\"/users/{user_id}/events/copy-groups\",\n                 json={\"sourceUser\": teammate_id, \"senderId\": BOT_USER})[\"Event\"]\n    text = (f\"&lt;@{command['user_id']}&gt; asks for the same groups as &lt;@{teammate}&gt; in PVML: \"\n            + \", \".join(f\"*{n}*\" for n in names) + \".\")\n    value = json.dumps({\"event\": event[\"id\"], \"requester\": command[\"user_id\"], \"user\": user_id})\n    if not ask_owners(client, respond, text, decision_buttons(text, value)):\n        return pvml(\"DELETE\", f\"/cancel/{event['id']}\")\n    respond(f\"Your request for the same groups as &lt;@{teammate}&gt; was sent to the data owners.\")\n\n\nREASON_BOX = {\"type\": \"input\", \"block_id\": \"reason\", \"optional\": True,\n              \"label\": {\"type\": \"plain_text\", \"text\": \"Reason, if you reject (optional)\"},\n              \"element\": {\"type\": \"plain_text_input\", \"action_id\": \"text\", \"max_length\": 300,\n                          \"placeholder\": {\"type\": \"plain_text\", \"text\": \"The requester sees it\"}}}\n\n\ndef typed_reason(body):\n    # The text an approver typed in the reason box of the request message\n    return next((e.get(\"value\") or \"\" for b in body.get(\"state\", {}).get(\"values\", {}).values()\n                 for e in b.values() if e.get(\"type\") == \"plain_text_input\"), \"\").strip()\n\n\ndef reason_note(reason):\n    return f\"\\nReason: {reason}\" if reason else \"\"\n\n\ndef decide(client, approver, info, channel, ts, text, result, reason=\"\"):\n    if approver not in APPROVERS:\n        return client.chat_postEphemeral(channel=channel, user=approver, text=\"Only data owners can approve requests.\")\n    status = pvml(\"GET\", f\"/events/eventId/{info['event']}\")[\"event\"][\"status\"]\n    if status != \"pending\":\n        return done(client, channel, ts, f\"{text}\\nAlready handled in PVML: *{status}*.\")\n    try:\n        pvml(\"POST\", f\"/resolve/{info['event']}/by/{BOT_USER}\", json={\"result\": result})\n    except requests.HTTPError:  # another approver was faster: the first decision wins\n        status = pvml(\"GET\", f\"/events/eventId/{info['event']}\")[\"event\"][\"status\"]\n        return done(client, channel, ts, f\"{text}\\nAlready handled in PVML: *{status}*.\")\n    done(client, channel, ts, f\"{text}\\n*{result.capitalize()}* by &lt;@{approver}&gt;.\" + reason_note(reason))\n    dm = f\"Your data access request was {result}.\" + reason_note(reason)\n    if result == \"approved\":\n        if \"group\" in info:  # /access: the VDBs of the new group\n            lines, title = mcp_lines(pvml(\"GET\", f\"/groups/{info['group']}/vdbs\")[\"vdbs\"] or []), \"Your new VDBs\"\n        else:  # /access-like: all the user's VDBs now\n            lines, title = mcp_lines(pvml(\"GET\", f\"/users/{info['user']}/vdbs\")[\"vdbs\"] or []), \"Your VDBs\"\n        if lines:\n            dm += f\"\\n{title} and their MCP servers:\\n\" + \"\\n\".join(lines) + \"\\n\" + MCP_HELP\n    client.chat_postMessage(channel=info[\"requester\"], text=dm)\n\n\n@app.action(\"approve\")\ndef approve(ack, body, client):\n    ack()\n    decide(client, body[\"user\"][\"id\"], json.loads(body[\"actions\"][0][\"value\"]),\n           body[\"channel\"][\"id\"], body[\"message\"][\"ts\"], body[\"message\"][\"text\"], \"approved\")\n\n\n@app.action(\"reject\")\n@app.action(\"conn_reject\")\ndef show_reason_box(ack, body, client):\n    # First Reject click: add the reason box to the card and turn Reject into \"Confirm reject\"\n    ack()\n    approver, channel = body[\"user\"][\"id\"], body[\"channel\"][\"id\"]\n    if approver not in APPROVERS:\n        return client.chat_postEphemeral(channel=channel, user=approver, text=\"Only data owners can approve requests.\")\n    blocks = body[\"message\"][\"blocks\"]\n    actions = blocks[-1]\n    for e in actions[\"elements\"]:\n        if e.get(\"action_id\") in (\"reject\", \"conn_reject\"):\n            e[\"action_id\"] += \"_final\"\n            e[\"text\"][\"text\"] = \"Confirm reject\"\n    client.chat_update(channel=channel, ts=body[\"message\"][\"ts\"], text=body[\"message\"][\"text\"],\n                       blocks=blocks[:-1] + [REASON_BOX, actions])\n\n\n@app.action(\"reject_final\")\ndef reject(ack, body, client):\n    ack()\n    decide(client, body[\"user\"][\"id\"], json.loads(body[\"actions\"][0][\"value\"]), body[\"channel\"][\"id\"],\n           body[\"message\"][\"ts\"], body[\"message\"][\"text\"], \"rejected\", typed_reason(body))\n\n\n@app.command(\"/my-vdbs\")\ndef my_vdbs(ack, command, client, respond):\n    ack()\n    email, user_id = slack_user_to_pvml(client, command[\"user_id\"])\n    if not user_id:\n        return respond(f\"You are not a user in PVML yet ({email}). Ask an admin to invite you.\")\n    lines = mcp_lines(pvml(\"GET\", f\"/users/{user_id}/vdbs\")[\"vdbs\"] or [])\n    if not lines:\n        return respond(\"You have no VDBs yet. Ask for a group with `/access`.\")\n    respond(\"Your VDBs and their MCP servers:\\n\" + \"\\n\".join(lines) + \"\\n\" + MCP_HELP)\n\n\n@app.command(\"/my-groups\")\ndef my_groups(ack, command, client, respond):\n    ack()\n    email, user_id = slack_user_to_pvml(client, command[\"user_id\"])\n    if not user_id:\n        return respond(f\"You are not a user in PVML yet ({email}). Ask an admin to invite you.\")\n    groups = pvml(\"GET\", f\"/users/{user_id}/groups\")[\"groups\"] or []\n    if not groups:\n        return respond(\"You are not in any group yet. Type `/access` to see the groups you can ask for.\")\n    respond(\"Your groups:\\n\" + \"\\n\".join(f\"- *{g['name']}*\" for g in groups))\n\n\n# ---------- ODBC / JDBC connection strings ----------\ndef days_option(days):\n    return {\"text\": {\"type\": \"plain_text\", \"text\": f\"{days} days\"}, \"value\": str(days)}\n\n\n@app.command(\"/connection\")\ndef request_connection(ack, command, client, respond):\n    ack()\n    text = clean(command[\"text\"])\n    name, _, last = text.rpartition(\" \")\n    vdb_name, days = (clean(name), int(last)) if last.isdigit() and name else (text, DEFAULT_DAYS)\n    days = max(1, min(days, MAX_DAYS))\n    email, user_id = slack_user_to_pvml(client, command[\"user_id\"])\n    if not user_id:\n        return respond(f\"You are not a user in PVML yet ({email}). Ask an admin to invite you.\")\n    vdbs = pvml(\"GET\", f\"/users/{user_id}/vdbs\")[\"vdbs\"] or []\n    vdb = next((v for v in vdbs if v[\"name\"].lower() == vdb_name.lower()), None)\n    if not vdb:\n        names = \", \".join(v[\"name\"] for v in vdbs) or \"none yet (ask for a group with `/access`)\"\n        return respond(f\"Type `/connection &lt;VDB name&gt; [days]`. Your VDBs: {names}\")\n    value = json.dumps({\"vdb\": vdb[\"vdbId\"], \"name\": vdb[\"name\"], \"user\": user_id, \"requester\": command[\"user_id\"]})\n    msg = f\"&lt;@{command['user_id']}&gt; asks for an ODBC/JDBC connection to *{vdb['name']}*. Pick how long it is valid:\"\n    choices = sorted({7, 30, 90, days} - {d for d in (7, 30, 90) if d &gt; MAX_DAYS})\n    if not ask_owners(client, respond, msg, [\n        {\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": msg}},\n        {\"type\": \"actions\", \"elements\": [\n            {\"type\": \"static_select\", \"action_id\": \"conn_days\", \"initial_option\": days_option(days),\n             \"options\": [days_option(d) for d in choices]},\n            {\"type\": \"button\", \"text\": {\"type\": \"plain_text\", \"text\": \"Approve\"}, \"style\": \"primary\",\n             \"action_id\": \"conn_approve\", \"value\": value},\n            {\"type\": \"button\", \"text\": {\"type\": \"plain_text\", \"text\": \"Reject\"}, \"style\": \"danger\",\n             \"action_id\": \"conn_reject\", \"value\": value}]}]):\n        return\n    respond(f\"Your request for a connection to *{vdb['name']}* ({days} days) was sent to the data owners.\")\n\n\n@app.action(\"conn_days\")\ndef pick_days(ack):\n    ack()  # the choice is read when the owner clicks Approve\n\n\n@app.action(\"conn_approve\")\n@app.action(\"conn_reject_final\")\ndef decide_connection(ack, body, client):\n    ack()\n    approver, action = body[\"user\"][\"id\"], body[\"actions\"][0]\n    info = json.loads(action[\"value\"])\n    channel, ts, text = body[\"channel\"][\"id\"], body[\"message\"][\"ts\"], body[\"message\"][\"text\"]\n    if approver not in APPROVERS:\n        return client.chat_postEphemeral(channel=channel, user=approver, text=\"Only data owners can approve requests.\")\n    if action[\"action_id\"] == \"conn_reject_final\":\n        reason = reason_note(typed_reason(body))\n        done(client, channel, ts, f\"{text}\\n*Rejected* by &lt;@{approver}&gt;.\" + reason)\n        return client.chat_postMessage(channel=info[\"requester\"],\n                                       text=f\"Your connection request for *{info['name']}* was rejected.\" + reason)\n    picked = [e[\"selected_option\"][\"value\"] for b in body[\"state\"][\"values\"].values()\n              for e in b.values() if e.get(\"type\") == \"static_select\" and e.get(\"selected_option\")]\n    days = min(int(picked[0]) if picked else DEFAULT_DAYS, MAX_DAYS)\n    done(client, channel, ts, f\"{text}\\n*Approved* for {days} days by &lt;@{approver}&gt;.\")\n    note = (f\"Your connection to *{info['name']}* was approved for {days} days. \"\n            \"The details are secret: they are shown once, only to you.\")\n    client.chat_postMessage(channel=info[\"requester\"], text=note, blocks=[\n        {\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": note}},\n        {\"type\": \"actions\", \"elements\": [{\"type\": \"button\", \"action_id\": \"conn_show\", \"style\": \"primary\",\n                                          \"text\": {\"type\": \"plain_text\", \"text\": \"Show my connection details\"},\n                                          \"value\": json.dumps({**info, \"days\": days})}]}])\n\n\nLABELS = {\"host\": \"Host\", \"url\": \"JDBC URL / Connection URL\", \"httpPath\": \"HTTP Path\", \"accessToken\": \"Access Token\",\n          \"token\": \"Token\", \"credentialFile\": \"JSON key file (save it as client.json)\"}\n\n\n@app.action(\"conn_show\")\ndef show_connection(ack, body, client):\n    ack()\n    info = json.loads(body[\"actions\"][0][\"value\"])\n    if body[\"user\"][\"id\"] != info[\"requester\"]:\n        return\n    view = client.views_open(trigger_id=body[\"trigger_id\"], view={  # must open within 3 seconds\n        \"type\": \"modal\", \"title\": {\"type\": \"plain_text\", \"text\": \"Connection details\"},\n        \"blocks\": [{\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": \"Creating your connection...\"}}]})[\"view\"]\n    expiry = int((time.time() + info[\"days\"] * 86400) * 1000)\n    conn = pvml(\"POST\", f\"/vdbs/{info['vdb']}/users/{info['user']}/connection_strings\",\n                json={\"name\": f\"slack: {info['name']}\", \"description\": \"Created by the PVML Access Slack bot\",\n                      \"expiration\": expiry})[\"DataSourceConnectionInfo\"]\n    blocks = [{\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\":\n               f\"*{info['name']}*, valid for {info['days']} days. Copy these into your tool now: \"\n               \"you can not see them again.\"}}]\n    for key, val in conn.items():\n        if key == \"credentialFile\":  # one line, so copying from Slack keeps it valid JSON\n            try:\n                val = json.dumps(json.loads(val))\n            except ValueError:\n                pass\n        if val:\n            blocks.append({\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\",\n                           \"text\": f\"*{LABELS.get(key, key)}*\\n```{val}```\"[:3000]}})\n    client.views_update(view_id=view[\"id\"], view={\n        \"type\": \"modal\", \"title\": {\"type\": \"plain_text\", \"text\": \"Connection details\"},\n        \"close\": {\"type\": \"plain_text\", \"text\": \"Done\"}, \"blocks\": blocks})\n    done(client, body[\"channel\"][\"id\"], body[\"message\"][\"ts\"],\n         f\"Your connection to *{info['name']}* was created (valid for {info['days']} days). \"\n         \"Lost the details? Ask again with `/connection`.\")\n\n\n# ---------- the bot's own PVML key: warn before it expires, fail clearly if it stops working ----------\n_last_alert = {\"rejected\": 0.0, \"expiry\": 0.0}\n\n\ndef alert_owners(kind, text, every_seconds):\n    if time.time() - _last_alert[kind] &lt; every_seconds:\n        return\n    _last_alert[kind] = time.time()\n    try:\n        app.client.chat_postMessage(channel=CHANNEL, text=text)\n    except Exception as e:\n        print(f\"Can not alert the data owners: {e}\")\n\n\n@app.error\ndef on_error(error, body):\n    if not isinstance(error, KeyRejected):\n        print(f\"Error: {error}\")\n        return\n    alert_owners(\"rejected\", \":warning: My PVML API key does not work anymore (expired or revoked). \"\n                 \"Access requests are paused. A PVML admin must give me a new key.\", 3600)\n    if body.get(\"response_url\"):\n        requests.post(body[\"response_url\"], timeout=10, json={\"response_type\": \"ephemeral\", \"text\":\n                      \"Access requests are paused for a moment. The data owners were told.\"})\n\n\ndef check_key_expiry():\n    if not KEY_ID:\n        return\n    try:\n        keys = pvml(\"GET\", \"/token\")[\"tokensMetadata\"] or []\n    except KeyRejected:\n        return alert_owners(\"rejected\", \":warning: My PVML API key does not work anymore (expired or revoked). \"\n                            \"Access requests are paused. A PVML admin must give me a new key.\", 3600)\n    key = next((k for k in keys if k[\"Id\"] == KEY_ID), None)\n    if key:\n        days = (key[\"ExpirationDate\"] / 1000 - time.time()) / 86400\n        if days &lt;= WARN_DAYS:\n            day = time.strftime(\"%b %d, %Y\", time.gmtime(key[\"ExpirationDate\"] / 1000))\n            alert_owners(\"expiry\", f\":warning: My PVML API key expires on {day}. \"\n                         \"Please ask a PVML admin to give me a new key before that.\", 86400)\n\n\ndef key_check_loop():\n    check_key_expiry()\n    timer = threading.Timer(12 * 3600, key_check_loop)  # twice a day\n    timer.daemon = True\n    timer.start()\n\n\nif __name__ == \"__main__\":\n    key_check_loop()\n    SocketModeHandler(app, os.environ[\"SLACK_APP_TOKEN\"]).start()\n</code></pre>\n<h3 id=\"step-4-run-it-and-check-the-result\">Step 4: Run it and check the result</h3>\n<ol>\n<li>Run <code>python bot.py</code>. The log shows <code>Bolt app is running!</code>.</li>\n<li>In Slack, type <code>/access</code>. You see the list of groups.</li>\n<li>Type <code>/access</code> and a group name. The request appears in the owners' channel.</li>\n<li>As an approver, click <strong>Approve</strong>. The buttons change to \"Approved by @you\", and you get a direct message with the MCP servers of the group's VDBs. In PVML, the user is now in the group.</li>\n<li>Type <code>/access-like</code> and tag a teammate. The request lists the teammate's groups. Approve it, and you are in the same groups.</li>\n<li>Type <code>/connection</code> and a VDB name, approve it, and click <strong>Show my connection details</strong>. The pop-up shows the connection details once.</li>\n</ol>\n<h3 id=\"step-5-run-it-in-production\">Step 5: Run it in production</h3>\n<p>The app is one small, always-on process. Run only one copy: two copies would send each warning twice.</p>\n<p>Save two more files in the same folder as <code>bot.py</code>. Each is plain text: open a text editor, paste the block, and save it with the exact name shown.</p>\n<p><code>requirements.txt</code>:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>slack_bolt==1.30.0\nrequests==2.34.2\n</code></pre><p><code>Dockerfile</code>, with no file extension (not <code>Dockerfile.txt</code>). It tells Docker how to build the app, and makes it run as an unprivileged user:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-dockerfile\">FROM python:3.12-slim\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\nCOPY bot.py .\nRUN chmod 644 bot.py\nUSER nobody\nCMD [\"python\", \"-u\", \"bot.py\"]\n</code></pre>\n<p><strong>On a VM with Docker:</strong> put the settings from Step 3 in a file <code>bot.env</code> in the same folder (one <code>NAME=value</code> per line, readable only by you). Copy the four files to the VM. Then, on the VM:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>docker build -t pvml-access-bot .\ndocker run -d --name pvml-access-bot --restart=always --env-file bot.env pvml-access-bot\ndocker logs pvml-access-bot\n</code></pre><p><code>--restart=always</code> starts the app again after a crash or a reboot. When the log shows <code>Bolt app is running!</code>, delete <code>bot.env</code> from the VM (<code>rm bot.env</code>): the container keeps the settings. To update the code, build again and replace the container (<code>docker rm -f pvml-access-bot</code>, then <code>docker run</code> again).</p>\n<p><strong>Example: a free-tier VM on Google Cloud.</strong> Run these from the app folder on your computer, with the <a href=\"https://cloud.google.com/sdk/docs/install\">gcloud CLI</a> signed in to a project with billing turned on. This VM should be <a href=\"https://docs.cloud.google.com/free/docs/free-cloud-features\">free</a>. It has Docker built in, no cloud permissions, and SSH only through Google's secure tunnel (IAP).</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>gcloud services enable compute.googleapis.com iap.googleapis.com\ngcloud compute instances create pvml-access-bot --zone=us-central1-a \\\n  --machine-type=e2-micro --image-family=cos-stable --image-project=cos-cloud \\\n  --boot-disk-type=pd-standard --no-service-account --no-scopes\ngcloud compute firewall-rules create allow-ssh-from-iap --network=default \\\n  --allow=tcp:22 --source-ranges=35.235.240.0/20\ngcloud compute scp bot.py requirements.txt Dockerfile bot.env \\\n  pvml-access-bot:~ --zone=us-central1-a --tunnel-through-iap\ngcloud compute ssh pvml-access-bot --zone=us-central1-a --tunnel-through-iap\n</code></pre><p>In the SSH session, run the <code>docker</code> commands above. To close other ways in, you can delete the default rule that allows SSH from anywhere: <code>gcloud compute firewall-rules delete default-allow-ssh</code>. On AWS or Azure, a small VM with Docker works the same way.</p>\n<p><strong>On Kubernetes:</strong> build the image for your servers and push it to your registry (on an Apple silicon Mac, add <code>--platform linux/amd64</code>):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>docker build --platform linux/amd64 -t YOUR_REGISTRY/pvml-access-bot:1.0 .\ndocker push YOUR_REGISTRY/pvml-access-bot:1.0\n</code></pre><p>Put the tokens and the PVML key in a secret, and run one copy (<code>k8s.yaml</code>):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-yaml\"># Create the secret first (values from your secret manager), for example:\n# kubectl create secret generic pvml-access-bot \\\n#   --from-literal=SLACK_BOT_TOKEN=xoxb-... --from-literal=SLACK_APP_TOKEN=xapp-... \\\n#   --from-literal=PVML_API_KEY=...\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: pvml-access-bot\nspec:\n  replicas: 1            # run only one copy\n  selector:\n    matchLabels: {app: pvml-access-bot}\n  template:\n    metadata:\n      labels: {app: pvml-access-bot}\n    spec:\n      containers:\n        - name: bot\n          image: YOUR_REGISTRY/pvml-access-bot:1.0\n          envFrom:\n            - secretRef: {name: pvml-access-bot}\n          env:\n            - {name: PVML_BASE_URL, value: \"https://platform.pvml.com\"}\n            - {name: PVML_WORKSPACE_ID, value: \"YOUR_WORKSPACE_ID\"}\n            - {name: PVML_BOT_USER_ID, value: \"BOT_SERVICE_ACCOUNT_ID\"}\n            - {name: SLACK_APPROVAL_CHANNEL, value: \"C0123456789\"}\n            - {name: SLACK_APPROVERS, value: \"U0123456789,U0987654321\"}\n            - {name: CONN_DEFAULT_DAYS, value: \"30\"}\n            - {name: CONN_MAX_DAYS, value: \"90\"}\n          resources:\n            requests: {cpu: 50m, memory: 64Mi}\n            limits: {memory: 256Mi}\n</code></pre>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>kubectl apply -f k8s.yaml\nkubectl logs deploy/pvml-access-bot\n</code></pre><p>The same container also runs on AWS ECS, Azure Container Apps and similar services.</p>\n<h3 id=\"troubleshooting\">Troubleshooting</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>What you see</th>\n<th>Why</th>\n<th>What to do</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>\"You are not a user in PVML yet\"</td>\n<td>The Slack email is not a user in the PVML workspace.</td>\n<td>Invite the person to PVML, or sync users with SCIM.</td>\n</tr>\n<tr>\n<td>\"I could not reach the data owners\"</td>\n<td>The app is not in the owners' channel.</td>\n<td>Type <code>/invite @PVML Access</code> in that channel.</td>\n</tr>\n<tr>\n<td>\"Access requests are paused\"</td>\n<td>PVML does not accept the app's key (expired or revoked).</td>\n<td>Create a new key (Step 1) and restart the app.</td>\n</tr>\n<tr>\n<td>\"Already handled in PVML\"</td>\n<td>Another approver, or an admin in the platform, decided first.</td>\n<td>Nothing: the first decision counts.</td>\n</tr>\n<tr>\n<td>\"API [compute.googleapis.com] not enabled\" (Google Cloud example)</td>\n<td>The project has no billing account, so the first command failed.</td>\n<td>Link a billing account to the project, then run the commands again.</td>\n</tr>\n<tr>\n<td>\"Failed to lookup instance\" on <code>gcloud compute scp</code> or <code>ssh</code></td>\n<td>The new VM is not ready for the secure tunnel yet, or it was not created.</td>\n<td>Run <code>gcloud compute instances list</code>. If the VM is there, wait a minute and try again. If not, run the create command again.</td>\n</tr>\n<tr>\n<td>A command does nothing</td>\n<td>The app is not running, or a new command is missing in Slack.</td>\n<td>Check the log. After changing the manifest, save it in the Slack app settings.</td>\n</tr>\n</tbody>\n</table>\n</div><h3 id=\"good-to-know\">Good to know</h3>\n<ul>\n<li><strong>Who approved?</strong> In PVML, the request shows the app's service account as the approver. The Slack message shows who clicked.</li>\n<li><strong>Reject with a reason:</strong> when an approver clicks <strong>Reject</strong>, a box opens on the request for an optional reason. They click <strong>Confirm reject</strong>, and the requester gets the reason in the direct message.</li>\n<li><strong>Several approvers:</strong> everyone in the channel sees the request. The first decision wins, and the buttons show who decided.</li>\n<li><strong>Requests also show in the platform,</strong> under the bell icon. Admins can approve there too.</li>\n<li><strong>Connection strings are secrets.</strong> The app shows them only in a one-time pop-up, never in a message. Admins can see and revoke them under <strong>Connections &gt; ODBC / JDBC</strong>, and each one stops working on its expiry date.</li>\n<li><strong>MCP URLs are not secrets.</strong> Each person signs in with their own company account and gets only their own access.</li>\n<li><strong>Works with PVML Cloud and self-hosted PVML.</strong> The app needs only outbound access to Slack and to PVML, with no open ports. Set <code>PVML_BASE_URL</code> for self-hosted PVML.</li>\n<li><strong>One app per PVML workspace.</strong> The API key belongs to one workspace. For more workspaces, run one app per workspace, with different command names (for example <code>/access-finance</code>).</li>\n</ul>\n<h3 id=\"next-steps\">Next steps</h3>\n<ul>\n<li>Make it your own: for example, send requests to your ticketing tool instead of Slack.</li>\n<li>Read more about the API calls in <strong>Access Requests</strong>, <strong>Connections &gt; ODBC / JDBC</strong> and <strong>GenAI &gt; MCP</strong>.</li>\n<li>Questions about the PVML API? Contact <a href=\"mailto:support@pvml.com\">support@pvml.com</a>.</li>\n</ul>\n","_postman_id":"3aceca6a-7051-4c07-801d-442fea5a8922","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Request to Join a Group","id":"c029b26e-b2ef-41cc-a9c1-23546690a816","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"userToAdd\": \"{{user_id}}\",\n    \"senderId\": \"{{user_id}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/events/join","description":"<p>Sends a request to add a user to a group. The admins of the workspace see it under the bell icon, and with <strong>Get Requests to Handle</strong>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>group_id</code></strong> (string): The group to join.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>userToAdd</code></strong> (string): The user to add to the group. Usually the person who asks.</li>\n<li><strong><code>senderId</code></strong> (string): The user who sends the request. Use the user ID of the API key.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p><strong><code>Event</code></strong>: the new request, with <code>status</code> <code>pending</code>.</p>\n<ul>\n<li><strong><code>id</code></strong> (string): The request ID.</li>\n<li><strong><code>type</code></strong> (string): <code>join_group</code> or <code>copy_groups</code>.</li>\n<li><strong><code>data</code></strong> (string): A JSON string with the details: for <code>join_group</code> the <code>groupId</code>, <code>groupName</code>, <code>userId</code> and email of the user to add; for <code>copy_groups</code> the source and target users.</li>\n<li><strong><code>status</code></strong> (string): <code>pending</code>, <code>approved</code>, <code>rejected</code> or <code>canceled</code>.</li>\n<li><strong><code>senderId</code></strong>, <strong><code>senderEmail</code></strong>: Who asked.</li>\n<li><strong><code>handledById</code></strong>, <strong><code>handledByEmail</code></strong>, <strong><code>handleTime</code></strong>: Who approved or rejected it, and when (empty while <code>pending</code>).</li>\n<li><strong><code>creationTime</code></strong> (string): When it was sent.</li>\n</ul>\n<p>Any workspace member can send a request.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","groups","{{group_id}}","events","join"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"04e68c1b-c0a9-437c-90f2-11c121e20212","name":"Request to Join a Group - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"userToAdd\": \"{{user_id}}\",\n    \"senderId\": \"{{user_id}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/groups/{{group_id}}/events/join"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"Event\": {\n        \"creationTime\": \"2026-10-31T12:50:09.215Z\",\n        \"type\": \"join_group\",\n        \"data\": \"{\\\"groupId\\\":\\\"d8c07ec8-f3e6-5482-99c3-e708d18bebab\\\",\\\"userId\\\":\\\"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\\\",\\\"groupName\\\":\\\"finance analysts\\\",\\\"UserEmail\\\":\\\"dana.levi@example.com\\\"}\",\n        \"senderId\": \"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\",\n        \"receiverId\": \"\",\n        \"receiversRole\": \"admin\",\n        \"id\": \"6ae5e40127f5c2457a55ec5f\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"status\": \"pending\",\n        \"handledById\": \"\",\n        \"handleTime\": \"1970-01-01T00:00:00Z\",\n        \"senderEmail\": \"dana.levi@example.com\",\n        \"receiverEmail\": \"\",\n        \"handledByEmail\": \"\"\n    }\n}"}],"_postman_id":"c029b26e-b2ef-41cc-a9c1-23546690a816"},{"name":"Request Same Groups as Another User","id":"5e18f675-bf77-41de-997b-882642781c5d","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"sourceUser\": \"{{source_user_id}}\",\n    \"senderId\": \"{{user_id}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/events/copy-groups","description":"<p>Sends a request to give a user the same groups as another user. For example, a new analyst asks for the same access as a teammate.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): The user who gets the groups.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>sourceUser</code></strong> (string): The user whose groups are copied.</li>\n<li><strong><code>senderId</code></strong> (string): The user who sends the request. Use the user ID of the API key.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p><strong><code>Event</code></strong>: the new request, with <code>status</code> <code>pending</code>. Same fields as in <strong>Request to Join a Group</strong>.</p>\n<p>When it is approved, the user is added to every group of the source user. Groups the user already has stay as they are.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","events","copy-groups"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"f4de18b6-cadd-4127-a8c9-9d84ab0a95ee","name":"Request Same Groups as Another User - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"sourceUser\": \"{{source_user_id}}\",\n    \"senderId\": \"{{user_id}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/events/copy-groups"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"Event\": {\n        \"creationTime\": \"2026-10-31T12:50:09.215Z\",\n        \"type\": \"copy_groups\",\n        \"data\": \"{\\\"sourceUser\\\":\\\"4599af59-6602-58a4-97e0-a0b5016bcc58\\\",\\\"sourceUserEmail\\\":\\\"jane.doe@example.com\\\",\\\"targetUser\\\":\\\"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\\\",\\\"targetUserEmail\\\":\\\"dana.levi@example.com\\\"}\",\n        \"senderId\": \"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\",\n        \"receiverId\": \"\",\n        \"receiversRole\": \"admin\",\n        \"id\": \"6ae5e40127f5c2457a55ec5f\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"status\": \"pending\",\n        \"handledById\": \"\",\n        \"handleTime\": \"1970-01-01T00:00:00Z\",\n        \"senderEmail\": \"dana.levi@example.com\",\n        \"receiverEmail\": \"\",\n        \"handledByEmail\": \"\"\n    }\n}"}],"_postman_id":"5e18f675-bf77-41de-997b-882642781c5d"},{"name":"Get Requests to Handle","id":"95988b10-4cf5-4edc-a2c2-ae562f961c6b","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/events/receiver/{{user_id}}","description":"<p>Lists the requests that an admin can handle: pending ones and the history.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): The admin's own user ID (the user of the API key).</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p><strong><code>events</code></strong> (array):</p>\n<ul>\n<li><strong><code>id</code></strong> (string): The request ID.</li>\n<li><strong><code>type</code></strong> (string): <code>join_group</code> or <code>copy_groups</code>.</li>\n<li><strong><code>data</code></strong> (string): A JSON string with the details: for <code>join_group</code> the <code>groupId</code>, <code>groupName</code>, <code>userId</code> and email of the user to add; for <code>copy_groups</code> the source and target users.</li>\n<li><strong><code>status</code></strong> (string): <code>pending</code>, <code>approved</code>, <code>rejected</code> or <code>canceled</code>.</li>\n<li><strong><code>senderId</code></strong>, <strong><code>senderEmail</code></strong>: Who asked.</li>\n<li><strong><code>handledById</code></strong>, <strong><code>handledByEmail</code></strong>, <strong><code>handleTime</code></strong>: Who approved or rejected it, and when (empty while <code>pending</code>).</li>\n<li><strong><code>creationTime</code></strong> (string): When it was sent.</li>\n</ul>\n<p>💡 Show only <code>pending</code> requests in your approval flow.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","events","receiver","{{user_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"dd4757cb-46c9-4092-8e10-94de7286623a","name":"Get Requests to Handle - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/events/receiver/{{user_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"events\": [\n        {\n            \"creationTime\": \"2026-10-31T12:50:09.215Z\",\n            \"type\": \"join_group\",\n            \"data\": \"{\\\"groupId\\\":\\\"d8c07ec8-f3e6-5482-99c3-e708d18bebab\\\",\\\"userId\\\":\\\"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\\\",\\\"groupName\\\":\\\"finance analysts\\\",\\\"UserEmail\\\":\\\"dana.levi@example.com\\\"}\",\n            \"senderId\": \"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\",\n            \"receiverId\": \"\",\n            \"receiversRole\": \"admin\",\n            \"id\": \"6ae5e40127f5c2457a55ec5f\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"status\": \"pending\",\n            \"handledById\": \"\",\n            \"handleTime\": \"1970-01-01T00:00:00Z\",\n            \"senderEmail\": \"dana.levi@example.com\",\n            \"receiverEmail\": \"\",\n            \"handledByEmail\": \"\"\n        }\n    ]\n}"}],"_postman_id":"95988b10-4cf5-4edc-a2c2-ae562f961c6b"},{"name":"Get My Requests","id":"20cf4564-2173-408a-b1bb-32e1fe2a7b63","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/events/sender/{{user_id}}","description":"<p>Lists the requests a user sent, with their status. Use it to show the user if their request was approved.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): Your own user ID (the user of the API key).</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p><strong><code>events</code></strong> (array): same fields as in <strong>Get Requests to Handle</strong>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","events","sender","{{user_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"297de49c-73fd-49c7-b550-ae01b5ac8e6f","name":"Get My Requests - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/events/sender/{{user_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"events\": [\n        {\n            \"creationTime\": \"2026-10-31T12:50:09.215Z\",\n            \"type\": \"join_group\",\n            \"data\": \"{\\\"groupId\\\":\\\"d8c07ec8-f3e6-5482-99c3-e708d18bebab\\\",\\\"userId\\\":\\\"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\\\",\\\"groupName\\\":\\\"finance analysts\\\",\\\"UserEmail\\\":\\\"dana.levi@example.com\\\"}\",\n            \"senderId\": \"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\",\n            \"receiverId\": \"\",\n            \"receiversRole\": \"admin\",\n            \"id\": \"6ae5e40127f5c2457a55ec5f\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"status\": \"approved\",\n            \"handledById\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n            \"handleTime\": \"2026-10-31T13:02:41.118Z\",\n            \"senderEmail\": \"dana.levi@example.com\",\n            \"receiverEmail\": \"\",\n            \"handledByEmail\": \"jane.doe@example.com\"\n        }\n    ]\n}"}],"_postman_id":"20cf4564-2173-408a-b1bb-32e1fe2a7b63"},{"name":"Get Request","id":"4f14ca3c-8338-4f95-aac9-a50bc05ae013","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/events/eventId/{{event_id}}","description":"<p>Returns one request.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>event_id</code></strong> (string): The request ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p><strong><code>event</code></strong>: the request. Same fields as in <strong>Get Requests to Handle</strong>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","events","eventId","{{event_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"8cfc8a4e-c8ee-4d15-9664-efa155fee765","name":"Get Request - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/events/eventId/{{event_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"event\": {\n        \"creationTime\": \"2026-10-31T12:50:09.215Z\",\n        \"type\": \"join_group\",\n        \"data\": \"{\\\"groupId\\\":\\\"d8c07ec8-f3e6-5482-99c3-e708d18bebab\\\",\\\"userId\\\":\\\"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\\\",\\\"groupName\\\":\\\"finance analysts\\\",\\\"UserEmail\\\":\\\"dana.levi@example.com\\\"}\",\n        \"senderId\": \"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\",\n        \"receiverId\": \"\",\n        \"receiversRole\": \"admin\",\n        \"id\": \"6ae5e40127f5c2457a55ec5f\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"status\": \"approved\",\n        \"handledById\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n        \"handleTime\": \"2026-10-31T13:02:41.118Z\",\n        \"senderEmail\": \"dana.levi@example.com\",\n        \"receiverEmail\": \"\",\n        \"handledByEmail\": \"jane.doe@example.com\"\n    }\n}"}],"_postman_id":"4f14ca3c-8338-4f95-aac9-a50bc05ae013"},{"name":"Approve or Reject Request","id":"af11056f-d58a-4d4d-8599-fb2b3905578f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"result\": \"approved\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/resolve/{{event_id}}/by/{{user_id}}","description":"<p>Approves or rejects a request. When a request is approved, PVML makes the change right away: it adds the user to the group (or groups).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>event_id</code></strong> (string): The request ID.</li>\n<li><strong><code>user_id</code></strong> (string): The admin who decides: your own user ID (the user of the API key).</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>result</code></strong> (string): <code>approved</code> or <code>rejected</code>.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200. Check the new status with <strong>Get Request</strong>.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","resolve","{{event_id}}","by","{{user_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"178fbed6-fdbb-448e-b7c1-90dfd17fa8a6","name":"Approve or Reject Request - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"result\": \"approved\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/resolve/{{event_id}}/by/{{user_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"null"}],"_postman_id":"af11056f-d58a-4d4d-8599-fb2b3905578f"},{"name":"Cancel Request","id":"cd7c2e1e-da7a-406f-ac8b-4bba8334ac45","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/cancel/{{event_id}}","description":"<p>Cancels a pending request. Its status becomes <code>canceled</code>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>event_id</code></strong> (string): The request ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","cancel","{{event_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"d7f83c81-d2c6-4942-a843-1425739d925d","name":"Cancel Request - 200 OK","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/cancel/{{event_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"null"}],"_postman_id":"cd7c2e1e-da7a-406f-ac8b-4bba8334ac45"},{"name":"Get Notifications","id":"9b2cc758-9bc9-4771-9ec1-7be0fd85be64","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/notifications/workspaces/{{workspace_id}}/users/{{user_id}}","description":"<p>Returns what a user sees under the bell icon in the platform: new requests, and other messages from the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): Your own user ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p><strong><code>events</code></strong> (array): same fields as in <strong>Get Requests to Handle</strong>. Messages that are not requests have other <code>type</code> values.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","notifications","workspaces","{{workspace_id}}","users","{{user_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"34b95ae2-bd47-43d8-a7f5-25651af479fe","name":"Get Notifications - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/notifications/workspaces/{{workspace_id}}/users/{{user_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"events\": [\n        {\n            \"creationTime\": \"2026-10-31T12:50:09.215Z\",\n            \"type\": \"join_group\",\n            \"data\": \"{\\\"groupId\\\":\\\"d8c07ec8-f3e6-5482-99c3-e708d18bebab\\\",\\\"userId\\\":\\\"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\\\",\\\"groupName\\\":\\\"finance analysts\\\",\\\"UserEmail\\\":\\\"dana.levi@example.com\\\"}\",\n            \"senderId\": \"7c2f1e0a-3b4d-5e6f-8a9b-0c1d2e3f4a5b\",\n            \"receiverId\": \"\",\n            \"receiversRole\": \"admin\",\n            \"id\": \"6ae5e40127f5c2457a55ec5f\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"status\": \"pending\",\n            \"handledById\": \"\",\n            \"handleTime\": \"1970-01-01T00:00:00Z\",\n            \"senderEmail\": \"dana.levi@example.com\",\n            \"receiverEmail\": \"\",\n            \"handledByEmail\": \"\"\n        }\n    ]\n}"}],"_postman_id":"9b2cc758-9bc9-4771-9ec1-7be0fd85be64"},{"name":"Dismiss Notifications","id":"f18e716c-589e-4335-ae6e-2894ac49d92f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"eventIds\": [\n        \"{{event_id}}\"\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/notifications/workspaces/{{workspace_id}}/users/{{user_id}}/dismiss","description":"<p>Removes notifications from the bell. The requests themselves do not change.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>user_id</code></strong> (string): Your own user ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>eventIds</code></strong> (array of strings, required): The notifications to dismiss.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>dismissedIds</code></strong> (array): The dismissed IDs.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","notifications","workspaces","{{workspace_id}}","users","{{user_id}}","dismiss"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"42908c2e-ce12-4332-b645-b66cf1a04e88","name":"Dismiss Notifications - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"eventIds\": [\n        \"{{event_id}}\"\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/notifications/workspaces/{{workspace_id}}/users/{{user_id}}/dismiss"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"dismissedIds\": [\n        \"6ae5e40127f5c2457a55ec5f\"\n    ]\n}"}],"_postman_id":"f18e716c-589e-4335-ae6e-2894ac49d92f"}],"id":"0e20fbc3-fb1d-4340-96f4-dbf979a302f4","description":"<p>Members can ask for access, and admins approve or reject the request. In the platform, admins see the requests under the <strong>bell icon</strong> at the top right. With the API, you can build the same flow into your own tools, for example a Slack bot or your ticketing system.</p>\n<h3 id=\"what-a-member-can-ask-for\">What a member can ask for</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Request</th>\n<th>What happens when it is approved</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Join a group</strong></td>\n<td>The user is added to the group, and gets all the VDBs of that group.</td>\n</tr>\n<tr>\n<td><strong>Same groups as another user</strong></td>\n<td>The user is added to every group of that user. Good for a new team member.</td>\n</tr>\n</tbody>\n</table>\n</div><h3 id=\"build-it-into-your-tools\">Build it into your tools</h3>\n<p>For example, a Slack bot: people type <code>/access finance analysts</code>, and data owners approve with one click. See <strong>Access Requests &gt; Tutorial: Build access requests in Slack</strong> for a step-by-step tutorial with working code.</p>\n<h3 id=\"statuses\">Statuses</h3>\n<p><code>pending</code> → <code>approved</code>, <code>rejected</code> or <code>canceled</code>.</p>\n<p>💡 The <code>user_id</code> in the path and the <code>senderId</code> are the user of the API key: a person, or the service account of a bot. Only admins can approve.</p>\n","_postman_id":"0e20fbc3-fb1d-4340-96f4-dbf979a302f4","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Datasources","item":[{"name":"Datasource CRUD","item":[{"name":"Connect Postgres","id":"5817f9f6-5a52-4b72-becb-ca10e7c96dc9","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my postgres datasource\",\n    \"description\": \"\",\n    \"type\": \"Postgres\",\n    \"data\": \"{\\\"hostName\\\": \\\"{{postgres_host}}\\\", \\\"port\\\": 5432, \\\"databaseName\\\": \\\"{{postgres_db_name}}\\\", \\\"username\\\": \\\"{{postgres_username}}\\\", \\\"password\\\": \\\"{{postgres_password}}\\\", \\\"tls\\\": true, \\\"trustServerCertificate\\\": false}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources","description":"<p>Connects a Postgres datasource to the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><strong><code>name</code></strong> (string): The datasource name.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A short description.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): <code>Postgres</code>.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): The connection details, as a JSON <strong>string</strong>:</p>\n</li>\n<li><p><code>hostName</code> (string): The database host.</p>\n</li>\n<li><p><code>port</code> (number): Usually 5432.</p>\n</li>\n<li><p><code>databaseName</code> (string).</p>\n</li>\n<li><p><code>username</code>, <code>password</code> (string).</p>\n</li>\n<li><p><code>tls</code> (boolean): Use TLS. Recommended.</p>\n</li>\n<li><p><code>trustServerCertificate</code> (boolean): Accept the server certificate without checking it. Use only for testing.</p>\n</li>\n</ul>\n<p>Or connect with one URL: set <code>urlMode</code> to <code>true</code> and send <code>connectionString</code> (for example <code>postgresql://user:password@host:5432/db</code>) instead of the fields above.</p>\n<p>💡 Run <strong>Test Datasource Connection</strong> first. After you connect, check <code>lastRefreshStatus</code> with <strong>Get Datasource</strong>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response is the new datasource object (status 201) with these fields:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): The unique identifier for the datasource.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): The name of the datasource.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A brief description of the datasource.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of the datasource, in this case: <em>Postgres</em>.</p>\n</li>\n<li><p><strong><code>structuredType</code></strong> (string): The type of the datasource used for the unstructured datasource structure (e.g., Postgres, MySQL, etc.).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): Configuration data for the datasource - always empty. To get this information, use the <em><strong>Get Datasource Connection Details</strong></em> API.</p>\n</li>\n<li><p><strong><code>snapshotTimestamp</code></strong> (string): The timestamp of the last snapshot taken for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshTrigger</code></strong> (string): The timestamp of the last refresh trigger for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshStatus</code></strong> (string): The status of the last refresh operation. Options:</p>\n<ul>\n<li><p>init</p>\n</li>\n<li><p>indexing</p>\n</li>\n<li><p>failed / completed</p>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"8f2f2291-1da6-4963-9604-bedd868ececd","name":"Connect Postgres - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my postgres datasource\",\n    \"description\": \"\",\n    \"type\": \"Postgres\",\n    \"data\": \"{\\\"hostName\\\": \\\"{{postgres_host}}\\\", \\\"port\\\": 5432, \\\"databaseName\\\": \\\"{{postgres_db_name}}\\\", \\\"username\\\": \\\"{{postgres_username}}\\\", \\\"password\\\": \\\"{{postgres_password}}\\\", \\\"tls\\\": true, \\\"trustServerCertificate\\\": false}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"754d0a48-2c90-52a7-ad1a-fe8f89160597\",\n    \"name\": \"my postgres datasource\",\n    \"description\": \"\",\n    \"type\": \"Postgres\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"lastRefreshStatus\": \"init\",\n    \"snapshotType\": \"\",\n    \"snapshotScanType\": \"\"\n}"}],"_postman_id":"5817f9f6-5a52-4b72-becb-ca10e7c96dc9"},{"name":"Connect BigQuery","id":"3d591e9a-f820-4308-a49c-206d1cd64c6a","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my bigquery datasource\",\n    \"description\": \"\",\n    \"type\": \"BigQuery\",\n    \"data\": \"{\\\"projectId\\\": \\\"{{bigquery_project_id}}\\\", \\\"credentials\\\": \\\"{{bigquery_service_account_key_json}}\\\"}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources","description":"<p>Connects a BigQuery datasource to the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><strong><code>name</code></strong> (string): The datasource name.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A short description.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): <code>BigQuery</code>.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): The connection details, as a JSON <strong>string</strong>:</p>\n</li>\n<li><p><code>projectId</code> (string): The Google Cloud project ID.</p>\n</li>\n<li><p><code>credentials</code> (string): The full content of the service account JSON key file, as a string.</p>\n</li>\n</ul>\n<p>💡 Run <strong>Test Datasource Connection</strong> first. After you connect, check <code>lastRefreshStatus</code> with <strong>Get Datasource</strong>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response is the new datasource object (status 201) with these fields:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): The unique identifier for the datasource.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): The name of the datasource.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A brief description of the datasource.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of the datasource, in this case: <em>BigQuery</em>.</p>\n</li>\n<li><p><strong><code>structuredType</code></strong> (string): The type of the datasource used for the unstructured datasource structure (e.g., Postgres, MySQL, etc.).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): Configuration data for the datasource - always empty. To get this information, use the <em><strong>Get Datasource Connection Details</strong></em> API.</p>\n</li>\n<li><p><strong><code>snapshotTimestamp</code></strong> (string): The timestamp of the last snapshot taken for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshTrigger</code></strong> (string): The timestamp of the last refresh trigger for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshStatus</code></strong> (string): The status of the last refresh operation. Options:</p>\n<ul>\n<li><p>init</p>\n</li>\n<li><p>indexing</p>\n</li>\n<li><p>failed / completed</p>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"033e8644-7c93-4969-b5a6-fe37608153c2","name":"Connect BigQuery - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my bigquery datasource\",\n    \"description\": \"\",\n    \"type\": \"BigQuery\",\n    \"data\": \"{\\\"projectId\\\": \\\"{{bigquery_project_id}}\\\", \\\"credentials\\\": \\\"{{bigquery_service_account_key_json}}\\\"}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"4b7307cb-f38b-574a-bd2e-d627d94aea91\",\n    \"name\": \"my bigquery datasource\",\n    \"description\": \"\",\n    \"type\": \"BigQuery\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"lastRefreshStatus\": \"init\",\n    \"snapshotType\": \"\",\n    \"snapshotScanType\": \"\"\n}"}],"_postman_id":"3d591e9a-f820-4308-a49c-206d1cd64c6a"},{"name":"Connect Databricks","id":"4be9851c-e081-4c99-a5b4-92bd007a2a57","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my databricks datasource\",\n    \"description\": \"\",\n    \"type\": \"Databricks\",\n    \"data\": \"{\\\"host_name\\\": \\\"{{databricks_hostname}}\\\", \\\"http_path\\\": \\\"{{databricks_http_path}}\\\", \\\"token\\\": \\\"{{databricks_token}}\\\"}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources","description":"<p>Connects a Databricks datasource to the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><strong><code>name</code></strong> (string): The datasource name.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A short description.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): <code>Databricks</code>.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): The connection details, as a JSON <strong>string</strong>:</p>\n</li>\n<li><p><code>host_name</code> (string): The server hostname of the SQL warehouse.</p>\n</li>\n<li><p><code>http_path</code> (string): The HTTP path of the SQL warehouse.</p>\n</li>\n<li><p><code>token</code> (string): A Databricks personal access token.</p>\n</li>\n</ul>\n<p>💡 Run <strong>Test Datasource Connection</strong> first. After you connect, check <code>lastRefreshStatus</code> with <strong>Get Datasource</strong>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response is the new datasource object (status 201) with these fields:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): The unique identifier for the datasource.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): The name of the datasource.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A brief description of the datasource.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of the datasource, in this case: <em>Databricks</em>.</p>\n</li>\n<li><p><strong><code>structuredType</code></strong> (string): The type of the datasource used for the unstructured datasource structure (e.g., Postgres, MySQL, etc.).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): Configuration data for the datasource - always empty. To get this information, use the <em><strong>Get Datasource Connection Details</strong></em> API.</p>\n</li>\n<li><p><strong><code>snapshotTimestamp</code></strong> (string): The timestamp of the last snapshot taken for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshTrigger</code></strong> (string): The timestamp of the last refresh trigger for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshStatus</code></strong> (string): The status of the last refresh operation. Options:</p>\n<ul>\n<li><p>init</p>\n</li>\n<li><p>indexing</p>\n</li>\n<li><p>failed / completed</p>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"b38a590f-bf52-4a23-b6c8-62badb4a60b5","name":"Connect Databricks - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my databricks datasource\",\n    \"description\": \"\",\n    \"type\": \"Databricks\",\n    \"data\": \"{\\\"host_name\\\": \\\"{{databricks_hostname}}\\\", \\\"http_path\\\": \\\"{{databricks_http_path}}\\\", \\\"token\\\": \\\"{{databricks_token}}\\\"}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"c6ef1773-2bca-5d1b-8009-dada489b1726\",\n    \"name\": \"my databricks datasource\",\n    \"description\": \"\",\n    \"type\": \"Databricks\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"lastRefreshStatus\": \"init\",\n    \"snapshotType\": \"\",\n    \"snapshotScanType\": \"\"\n}"}],"_postman_id":"4be9851c-e081-4c99-a5b4-92bd007a2a57"},{"name":"Connect Trino","id":"a1d545f9-ccbb-474a-8f4a-9b226a1f9b03","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my trino datasource\",\n    \"description\": \"\",\n    \"type\": \"Trino\",\n    \"data\": \"{\\\"address\\\": \\\"{{trino_host}}\\\", \\\"username\\\": \\\"{{trino_username}}\\\", \\\"password\\\": \\\"{{trino_password}}\\\", \\\"protocol\\\": \\\"https\\\", \\\"customCertificate\\\": \\\"{{trino_cert}}\\\"}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources","description":"<p>Connects a Trino datasource to the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><strong><code>name</code></strong> (string): The datasource name.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A short description.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): <code>Trino</code>.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): The connection details, as a JSON <strong>string</strong>:</p>\n</li>\n<li><p><code>address</code> (string): Host and port, for example <code>trino.example.com:443</code>.</p>\n</li>\n<li><p><code>username</code>, <code>password</code> (string).</p>\n</li>\n<li><p><code>protocol</code> (string): <code>https</code> or <code>http</code>.</p>\n</li>\n<li><p><code>customCertificate</code> (string, optional): A CA certificate (PEM) if your server uses a private CA.</p>\n</li>\n<li><p><code>clientCertificate</code>, <code>clientKey</code> (string, optional): For mutual TLS.</p>\n</li>\n</ul>\n<p>Or set <code>urlMode</code> to <code>true</code> and send <code>connectionString</code>.</p>\n<p>💡 Run <strong>Test Datasource Connection</strong> first. After you connect, check <code>lastRefreshStatus</code> with <strong>Get Datasource</strong>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response is the new datasource object (status 201) with these fields:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): The unique identifier for the datasource.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): The name of the datasource.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A brief description of the datasource.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of the datasource, in this case: <em>Trino</em>.</p>\n</li>\n<li><p><strong><code>structuredType</code></strong> (string): The type of the datasource used for the unstructured datasource structure (e.g., Postgres, MySQL, etc.).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): Configuration data for the datasource - always empty. To get this information, use the <em><strong>Get Datasource Connection Details</strong></em> API.</p>\n</li>\n<li><p><strong><code>snapshotTimestamp</code></strong> (string): The timestamp of the last snapshot taken for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshTrigger</code></strong> (string): The timestamp of the last refresh trigger for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshStatus</code></strong> (string): The status of the last refresh operation. Options:</p>\n<ul>\n<li><p>init</p>\n</li>\n<li><p>indexing</p>\n</li>\n<li><p>failed / completed</p>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"110fb634-7f88-4afb-a860-b6f9b2de3000","name":"Connect Trino - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my trino datasource\",\n    \"description\": \"\",\n    \"type\": \"Trino\",\n    \"data\": \"{\\\"address\\\": \\\"{{trino_host}}\\\", \\\"username\\\": \\\"{{trino_username}}\\\", \\\"password\\\": \\\"{{trino_password}}\\\", \\\"protocol\\\": \\\"https\\\", \\\"customCertificate\\\": \\\"{{trino_cert}}\\\"}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"a7cc5d90-7318-5611-9e6f-df5eecfbe336\",\n    \"name\": \"my trino datasource\",\n    \"description\": \"\",\n    \"type\": \"Trino\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"lastRefreshStatus\": \"init\",\n    \"snapshotType\": \"\",\n    \"snapshotScanType\": \"\"\n}"}],"_postman_id":"a1d545f9-ccbb-474a-8f4a-9b226a1f9b03"},{"name":"Connect MSSQL","id":"565e484a-eb5f-4560-b754-e6ef53c6857c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my mssql datasource\",\n    \"description\": \"\",\n    \"type\": \"MsSql\",\n    \"data\": \"{\\\"host\\\": \\\"{{mssql_host}}\\\", \\\"port\\\": 1433, \\\"database\\\": \\\"{{mssql_database}}\\\", \\\"user\\\": \\\"{{mssql_username}}\\\", \\\"password\\\": \\\"{{mssql_password}}\\\", \\\"tls\\\": true, \\\"trustServerCertificate\\\": false}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources","description":"<p>Connects a MsSql datasource to the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><strong><code>name</code></strong> (string): The datasource name.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A short description.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): <code>MsSql</code>.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): The connection details, as a JSON <strong>string</strong>:</p>\n</li>\n<li><p><code>host</code> (string): The server host.</p>\n</li>\n<li><p><code>port</code> (number): Usually 1433.</p>\n</li>\n<li><p><code>database</code> (string).</p>\n</li>\n<li><p><code>user</code>, <code>password</code> (string).</p>\n</li>\n<li><p><code>tls</code> (boolean): Use TLS. Recommended.</p>\n</li>\n<li><p><code>trustServerCertificate</code> (boolean): Accept the server certificate without checking it. Use only for testing.</p>\n</li>\n</ul>\n<p>💡 The field names for MSSQL are <code>host</code>, <code>user</code> and <code>database</code> (not <code>hostName</code>, <code>username</code>, <code>databaseName</code>).</p>\n<p>💡 Run <strong>Test Datasource Connection</strong> first. After you connect, check <code>lastRefreshStatus</code> with <strong>Get Datasource</strong>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response is the new datasource object (status 201) with these fields:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): The unique identifier for the datasource.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): The name of the datasource.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A brief description of the datasource.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of the datasource, in this case: <em>MsSql</em>.</p>\n</li>\n<li><p><strong><code>structuredType</code></strong> (string): The type of the datasource used for the unstructured datasource structure (e.g., Postgres, MySQL, etc.).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): Configuration data for the datasource - always empty. To get this information, use the <em><strong>Get Datasource Connection Details</strong></em> API.</p>\n</li>\n<li><p><strong><code>snapshotTimestamp</code></strong> (string): The timestamp of the last snapshot taken for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshTrigger</code></strong> (string): The timestamp of the last refresh trigger for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshStatus</code></strong> (string): The status of the last refresh operation. Options:</p>\n<ul>\n<li><p>init</p>\n</li>\n<li><p>indexing</p>\n</li>\n<li><p>failed / completed</p>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"501dd047-14fc-4546-aebf-6bafccf9aa27","name":"Connect MSSQL - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my mssql datasource\",\n    \"description\": \"\",\n    \"type\": \"MsSql\",\n    \"data\": \"{\\\"host\\\": \\\"{{mssql_host}}\\\", \\\"port\\\": 1433, \\\"database\\\": \\\"{{mssql_database}}\\\", \\\"user\\\": \\\"{{mssql_username}}\\\", \\\"password\\\": \\\"{{mssql_password}}\\\", \\\"tls\\\": true, \\\"trustServerCertificate\\\": false}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"275671ba-88d5-543e-b370-b1d19ca6d237\",\n    \"name\": \"my mssql datasource\",\n    \"description\": \"\",\n    \"type\": \"MsSql\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"lastRefreshStatus\": \"init\",\n    \"snapshotType\": \"\",\n    \"snapshotScanType\": \"\"\n}"}],"_postman_id":"565e484a-eb5f-4560-b754-e6ef53c6857c"},{"name":"Connect Snowflake","id":"4a2ebddf-3b09-4136-95c9-4a59ec276ecf","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my snowflake datasource\",\n    \"description\": \"\",\n    \"type\": \"Snowflake\",\n    \"data\": \"{\\\"account\\\": \\\"{{snowflake_account}}\\\", \\\"warehouse\\\": \\\"{{snowflake_warehouse}}\\\", \\\"username\\\": \\\"{{snowflake_username}}\\\", \\\"privateKey\\\": \\\"{{snowflake_private_key_pem}}\\\"}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources","description":"<p>Connects a Snowflake datasource to the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><strong><code>name</code></strong> (string): The datasource name.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A short description.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): <code>Snowflake</code>.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): The connection details, as a JSON <strong>string</strong>:</p>\n</li>\n<li><p><code>account</code> (string): Your Snowflake account identifier (for example <code>myorg-myaccount</code>).</p>\n</li>\n<li><p><code>warehouse</code> (string): The warehouse to run queries on.</p>\n</li>\n<li><p><code>username</code> (string): A Snowflake user with key-pair authentication.</p>\n</li>\n<li><p><code>privateKey</code> (string): The private key (PEM) of that user.</p>\n</li>\n</ul>\n<p>💡 Run <strong>Test Datasource Connection</strong> first. After you connect, check <code>lastRefreshStatus</code> with <strong>Get Datasource</strong>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response is the new datasource object (status 201) with these fields:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): The unique identifier for the datasource.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): The name of the datasource.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A brief description of the datasource.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of the datasource, in this case: <em>Snowflake</em>.</p>\n</li>\n<li><p><strong><code>structuredType</code></strong> (string): The type of the datasource used for the unstructured datasource structure (e.g., Postgres, MySQL, etc.).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): Configuration data for the datasource - always empty. To get this information, use the <em><strong>Get Datasource Connection Details</strong></em> API.</p>\n</li>\n<li><p><strong><code>snapshotTimestamp</code></strong> (string): The timestamp of the last snapshot taken for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshTrigger</code></strong> (string): The timestamp of the last refresh trigger for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshStatus</code></strong> (string): The status of the last refresh operation. Options:</p>\n<ul>\n<li><p>init</p>\n</li>\n<li><p>indexing</p>\n</li>\n<li><p>failed / completed</p>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"53784a23-163e-4821-8d07-5c99cf3431f2","name":"Connect Snowflake - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my snowflake datasource\",\n    \"description\": \"\",\n    \"type\": \"Snowflake\",\n    \"data\": \"{\\\"account\\\": \\\"{{snowflake_account}}\\\", \\\"warehouse\\\": \\\"{{snowflake_warehouse}}\\\", \\\"username\\\": \\\"{{snowflake_username}}\\\", \\\"privateKey\\\": \\\"{{snowflake_private_key_pem}}\\\"}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"269da0d5-5426-5d28-b879-925a41a393b3\",\n    \"name\": \"my snowflake datasource\",\n    \"description\": \"\",\n    \"type\": \"Snowflake\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"lastRefreshStatus\": \"init\",\n    \"snapshotType\": \"\",\n    \"snapshotScanType\": \"\"\n}"}],"_postman_id":"4a2ebddf-3b09-4136-95c9-4a59ec276ecf"},{"name":"Connect Redshift","id":"d47c10f0-c56c-48ee-80e4-060fdae34d9c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my redshift datasource\",\n    \"description\": \"\",\n    \"type\": \"Redshift\",\n    \"data\": \"{\\\"hostName\\\": \\\"{{redshift_host}}\\\", \\\"port\\\": 5439, \\\"databaseName\\\": \\\"{{redshift_db_name}}\\\", \\\"username\\\": \\\"{{redshift_username}}\\\", \\\"password\\\": \\\"{{redshift_password}}\\\", \\\"tls\\\": true, \\\"trustServerCertificate\\\": false}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources","description":"<p>Connects a Redshift datasource to the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><strong><code>name</code></strong> (string): The datasource name.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A short description.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): <code>Redshift</code>.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): The connection details, as a JSON <strong>string</strong>:</p>\n</li>\n<li><p><code>hostName</code> (string): The cluster endpoint host.</p>\n</li>\n<li><p><code>port</code> (number): Usually 5439.</p>\n</li>\n<li><p><code>databaseName</code> (string).</p>\n</li>\n<li><p><code>username</code>, <code>password</code> (string).</p>\n</li>\n<li><p><code>tls</code>, <code>trustServerCertificate</code> (boolean): As for Postgres.</p>\n</li>\n</ul>\n<p>💡 Run <strong>Test Datasource Connection</strong> first. After you connect, check <code>lastRefreshStatus</code> with <strong>Get Datasource</strong>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response is the new datasource object (status 201) with these fields:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): The unique identifier for the datasource.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): The name of the datasource.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A brief description of the datasource.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of the datasource, in this case: <em>Redshift</em>.</p>\n</li>\n<li><p><strong><code>structuredType</code></strong> (string): The type of the datasource used for the unstructured datasource structure (e.g., Postgres, MySQL, etc.).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): Configuration data for the datasource - always empty. To get this information, use the <em><strong>Get Datasource Connection Details</strong></em> API.</p>\n</li>\n<li><p><strong><code>snapshotTimestamp</code></strong> (string): The timestamp of the last snapshot taken for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshTrigger</code></strong> (string): The timestamp of the last refresh trigger for the datasource.</p>\n</li>\n<li><p><strong><code>lastRefreshStatus</code></strong> (string): The status of the last refresh operation. Options:</p>\n<ul>\n<li><p>init</p>\n</li>\n<li><p>indexing</p>\n</li>\n<li><p>failed / completed</p>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"5c110ab4-e1a2-4ae3-824b-c7795bcfaa34","name":"Connect Redshift - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"my redshift datasource\",\n    \"description\": \"\",\n    \"type\": \"Redshift\",\n    \"data\": \"{\\\"hostName\\\": \\\"{{redshift_host}}\\\", \\\"port\\\": 5439, \\\"databaseName\\\": \\\"{{redshift_db_name}}\\\", \\\"username\\\": \\\"{{redshift_username}}\\\", \\\"password\\\": \\\"{{redshift_password}}\\\", \\\"tls\\\": true, \\\"trustServerCertificate\\\": false}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"7fa6fbcd-2555-5a26-b7a0-c3309db08131\",\n    \"name\": \"my redshift datasource\",\n    \"description\": \"\",\n    \"type\": \"Redshift\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"lastRefreshStatus\": \"init\",\n    \"snapshotType\": \"\",\n    \"snapshotScanType\": \"\"\n}"}],"_postman_id":"d47c10f0-c56c-48ee-80e4-060fdae34d9c"},{"name":"Test Datasource Connection","id":"7a76ba51-cd93-4f57-af0a-e38b3c5abc03","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"type\": \"Postgres\",\n    \"data\": \"{\\\"hostName\\\": \\\"{{postgres_host}}\\\", \\\"port\\\": 5432, \\\"databaseName\\\": \\\"{{postgres_db_name}}\\\", \\\"username\\\": \\\"{{postgres_username}}\\\", \\\"password\\\": \\\"{{postgres_password}}\\\", \\\"tls\\\": true, \\\"trustServerCertificate\\\": false}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/test-connection","description":"<p>Tests the connection details of a datasource before you connect it.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>type</code></strong> (string): The datasource type: <code>Postgres</code>, <code>BigQuery</code>, <code>Databricks</code>, <code>Trino</code>, <code>MsSql</code>, <code>Snowflake</code>, <code>Redshift</code>, <code>MySql</code>, <code>SingleStore</code> or <code>S3</code>.</li>\n<li><strong><code>data</code></strong> (string): The connection details as a JSON string. The fields are the same as in the <strong>Connect</strong> request of that type.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>result</code></strong> (string): <code>success</code> (status 200) or <code>failed</code> (status 400).</li>\n</ul>\n<p>💡 If the test fails, check the host, port, network access from PVML and credentials.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","test-connection"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"1f98f100-4a07-43e6-af89-744bc82c038f","name":"Test Datasource Connection - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"type\": \"Postgres\",\n    \"data\": \"{\\\"hostName\\\": \\\"{{postgres_host}}\\\", \\\"port\\\": 5432, \\\"databaseName\\\": \\\"{{postgres_db_name}}\\\", \\\"username\\\": \\\"{{postgres_username}}\\\", \\\"password\\\": \\\"{{postgres_password}}\\\", \\\"tls\\\": true, \\\"trustServerCertificate\\\": false}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/test-connection"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"result\": \"success\"\n}"},{"id":"5d0883f4-9464-4c81-8285-31922a836b15","name":"Test Datasource Connection - 400 Failed","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"type\": \"Postgres\",\n    \"data\": \"{\\\"hostName\\\": \\\"{{postgres_host}}\\\", \\\"port\\\": 5432, \\\"databaseName\\\": \\\"{{postgres_db_name}}\\\", \\\"username\\\": \\\"{{postgres_username}}\\\", \\\"password\\\": \\\"{{postgres_password}}\\\", \\\"tls\\\": true, \\\"trustServerCertificate\\\": false}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/test-connection"},"status":"Bad Request","code":400,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"result\": \"failed\"\n}"}],"_postman_id":"7a76ba51-cd93-4f57-af0a-e38b3c5abc03"},{"name":"Get Datasource","id":"e961bb51-e863-4fcf-b867-d0cbec045e8c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}","description":"<p>This endpoint retrieves the details of a specific datasource within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>datasource_id</code> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response will be a <code>datasource</code> object in JSON format and will contain the following fields:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): A unique identifier for the datasource.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): The name of the datasource.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A description of the datasource. Can be empty.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of the datasource (e.g., \"Postgres\").</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): A unique identifier for the workspace associated with the datasource.</p>\n</li>\n<li><p><strong><code>snapshotTimestamp</code></strong> (string): The timestamp of the last snapshot in ISO 8601 format. Defaults to \"0001-01-01T00:00:00Z\" if not set.</p>\n</li>\n<li><p><strong>lastRefreshTrigger</strong> (string): The timestamp of the last refresh trigger in ISO 8601 format. Defaults to \"0001-01-01T00:00:00Z\" if not set.</p>\n</li>\n<li><p><strong><code>lastRefreshStatus</code></strong> (string): The status of the last refresh operation.</p>\n</li>\n</ul>\n<p><strong>💡</strong> use the <em><strong>Get Datasource Connection Details</strong></em> request in order to get the datasource connection properties.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"6ec76ede-7159-4177-8bcf-442a6e3a2b51","name":"Get Datasource - 200 Success","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"b11efcef-91ba-5428-858b-e9f180248708\",\n    \"name\": \"Transactions\",\n    \"description\": \"financial data\",\n    \"type\": \"Postgres\",\n    \"workspaceId\": \"6882e206-6136-58b5-af80-725482679afb\",\n    \"snapshotTimestamp\": \"2025-10-22T19:40:33.665386Z\",\n    \"lastRefreshTrigger\": \"2025-10-22T19:40:31.245112Z\",\n    \"lastRefreshStatus\": \"completed\",\n    \"snapshotType\": \"full\",\n    \"snapshotScanType\": \"full\"\n}"}],"_postman_id":"e961bb51-e863-4fcf-b867-d0cbec045e8c"},{"name":"Get Datasources","id":"6e3b0cf6-77bc-440d-8498-3ab90cc2f027","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources","description":"<p>This endpoint retrieves a list of datasources for a specific workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><code>workspace_id</code> (string): The ID of the workspace for which datasources are to be retrieved.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response will be a <code>datasources</code> object in JSON format and will contain the following fields:</p>\n<ul>\n<li><p><code>id</code> (string): The ID of the datasource.</p>\n</li>\n<li><p><code>name</code> (string): The name of the datasource.</p>\n</li>\n<li><p><code>description</code> (string): The description of the datasource.</p>\n</li>\n<li><p><code>type</code> (string): The type of the datasource (e.g., Postgres).</p>\n</li>\n<li><p><code>workspaceId</code> (string): The ID of the workspace to which the datasource belongs.</p>\n</li>\n<li><p><code>data</code> (string): The data associated with the datasource.</p>\n</li>\n<li><p><code>snapshotTimestamp</code> (string): The timestamp of the last snapshot of the datasource.</p>\n</li>\n<li><p><code>lastRefreshTrigger</code> (string): The trigger for the last refresh of the datasource.</p>\n</li>\n<li><p><code>lastRefreshStatus</code> (string): The status of the last refresh of the datasource.</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"8a545038-a766-43c0-a9e5-78fbf1b5f20a","name":"Get Datasources - 200 Success","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"datasources\": [\n        {\n            \"id\": \"5ee4f42f-7902-5365-8809-27cde6dc254f\",\n            \"name\": \"database\",\n            \"description\": \"\",\n            \"type\": \"BigQuery\",\n            \"workspaceId\": \"7595e582-c17f-535f-b531-b4ce291cdb9b\",\n            \"snapshotTimestamp\": \"2026-02-03T10:24:32.788075Z\",\n            \"lastRefreshStatus\": \"\",\n            \"snapshotType\": \"basic\",\n            \"snapshotScanType\": \"basic\"\n        },\n        {\n            \"id\": \"aec06374-51f7-58ae-a713-de892742411b\",\n            \"name\": \"databricks datasource\",\n            \"description\": \"\",\n            \"type\": \"Databricks\",\n            \"workspaceId\": \"7595e582-c17f-535f-b531-b4ce291cdb9b\",\n            \"lastRefreshStatus\": \"\",\n            \"snapshotType\": \"none\",\n            \"snapshotScanType\": \"none\"\n        },\n        {\n            \"id\": \"a79b5f5b-9306-5f9a-be24-06d139e39070\",\n            \"name\": \"Demo\",\n            \"description\": \"Test\",\n            \"type\": \"BigQuery\",\n            \"workspaceId\": \"7595e582-c17f-535f-b531-b4ce291cdb9b\",\n            \"snapshotTimestamp\": \"2026-02-12T14:34:58.493835Z\",\n            \"lastRefreshStatus\": \"\",\n            \"snapshotType\": \"basic\",\n            \"snapshotScanType\": \"basic\"\n        }\n    ]\n}"}],"_postman_id":"6e3b0cf6-77bc-440d-8498-3ab90cc2f027"},{"name":"Get Datasource Connection Details","id":"7d9a8b5d-3f8a-4a35-9a27-3bdfc509d29b","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/data/secure","description":"<p>This endpoint retrieves the connection string properties of a specific datasource within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>datasource_id</code> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response will be a datasource object in JSON format and will contain the following fields:</p>\n<ul>\n<li><p><code>id</code> (string): A unique identifier for the datasource.</p>\n</li>\n<li><p><code>name</code> (string): The name of the datasource.</p>\n</li>\n<li><p><code>description</code> (string): A description of the datasource. Can be empty.</p>\n</li>\n<li><p><code>type</code> (string): The type of the datasource (e.g., \"Postgres\").</p>\n</li>\n<li><p><code>workspaceId</code> (string): A unique identifier for the workspace associated with the datasource.</p>\n</li>\n<li><p><code>data</code> (string): The datasource connection properties.</p>\n</li>\n<li><p><code>snapshotTimestamp</code> (string): The timestamp of the last snapshot in ISO 8601 format. Defaults to \"0001-01-01T00:00:00Z\" if not set.</p>\n</li>\n<li><p><code>lastRefreshTrigger</code> (string): The timestamp of the last refresh trigger in ISO 8601 format. Defaults to \"0001-01-01T00:00:00Z\" if not set.</p>\n</li>\n<li><p><code>lastRefreshStatus</code> (string): The status of the last refresh operation.</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","data","secure"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"b699673c-9484-4653-9bd2-6acf2d18ab0b","name":"Get Datasource Connection Details - 200 Success","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/data/secure"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"4a7be1c8-eb2e-5a13-8ad5-2b43b502f559\",\n    \"name\": \"Transactions\",\n    \"description\": \"financial data\",\n    \"type\": \"Postgres\",\n    \"structuredType\": \"Postgres\",\n    \"workspaceId\": \"34bf3edd-67e8-56ee-b669-4a59ab869ada\",\n    \"data\": \"{\\\"hostName\\\":\\\"{{postgres_host}}\\\",\\\"username\\\":\\\"{{postgres_username}}\\\",\\\"port\\\":5432,\\\"databaseName\\\":\\\"db_catalog\\\",\\\"ssl\\\":true}\",\n    \"snapshotTimestamp\": \"2025-10-22T19:40:33.665386Z\",\n    \"lastRefreshStatus\": \"\",\n    \"snapshotType\": \"full\",\n    \"snapshotScanType\": \"full\"\n}"}],"_postman_id":"7d9a8b5d-3f8a-4a35-9a27-3bdfc509d29b"},{"name":"Update Datasource","id":"63e00ce2-e9d5-434e-b37c-1806100b1af0","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"databricks warehouse\",\n    \"description\": \"main analytics warehouse\",\n    \"type\": \"Databricks\",\n    \"data\": \"{\\\"host_name\\\": \\\"{{databricks_hostname}}\\\", \\\"http_path\\\": \\\"{{databricks_http_path}}\\\", \\\"token\\\": \\\"{{databricks_token}}\\\"}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}","description":"<p>This endpoint allows the user to update a specific datasource within a workspace.</p>\n<p>To update name or description, no need to send data (connection string details), just the datasource type.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>datasource_id</code> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><code>name</code> (string): The new name for the datasource.</p>\n</li>\n<li><p><code>description</code> (string): The new description for the datasource.</p>\n</li>\n<li><p><code>type</code> (string): The type of the datasource, e.g., \"Postgres\".</p>\n</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"21b4ef76-8740-4faa-96ab-d6fda097dab7","name":"Update Datasource - 200 Success","originalRequest":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"databricks warehouse\",\n    \"description\": \"main analytics warehouse\",\n    \"type\": \"Databricks\",\n    \"data\": \"{\\\"host_name\\\": \\\"{{databricks_hostname}}\\\", \\\"http_path\\\": \\\"{{databricks_http_path}}\\\", \\\"token\\\": \\\"{{databricks_token}}\\\"}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"Text","header":[],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"63e00ce2-e9d5-434e-b37c-1806100b1af0"},{"name":"Delete Datasource","id":"e9299437-1ab0-4d0b-acc6-2c6fe3d7b06f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}","description":"<p>This endpoint is used to delete a specific datasource within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>datasource_id</code> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not require a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response for this request will be empty upon successful deletion of the specified datasource.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"a250d4d7-2cfd-442c-ae0c-b2c4d5e90b05","name":"Delete Datasource - 200 Success","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"Text","header":[],"cookie":[],"responseTime":null,"body":"\"\""}],"_postman_id":"e9299437-1ab0-4d0b-acc6-2c6fe3d7b06f"},{"name":"Ping Datasource","id":"36ad803d-5ec3-4594-98fd-cbb30c61618f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/ping","description":"<p>This endpoint retrieves the ping status of a specific datasource within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>datasource_id</code> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><code>result</code> (string): <code>passed</code> when PVML can reach the datasource.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","ping"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"4e2a1816-3c26-4522-af2a-b19071a006b2","name":"Ping Datasource - 200 Success","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/ping"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"result\": \"passed\"\n}"}],"_postman_id":"36ad803d-5ec3-4594-98fd-cbb30c61618f"}],"id":"5d6869f7-1c20-431a-9ce3-daa699637122","description":"<p>Data sources can be connected to your PVML workspace using connection string properties, without the need to move or duplicate the data.</p>\n<p>Each datasource type requires specific connection parameters, so make sure you provide the appropriate values for the selected type (e.g., Snowflake, Databricks, Postgres, etc.).</p>\n<p>After successfully connecting a new datasource, choose a snapshot type.</p>\n<img src=\"https://content.pstmn.io/20d9ae45-4654-492c-b145-4fe9b6429cca/VW50aXRsZWQgZGVzaWduICgyKS5naWY=\" />","_postman_id":"5d6869f7-1c20-431a-9ce3-daa699637122","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Snapshot","item":[{"name":"Get Snapshot","id":"dfd79214-d411-49b0-898e-19c21d065400","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/tree","description":"<p>This endpoint retrieves the snapshot, tree structure (schemas) of a specific datasource within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>datasource_id</code> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p><code>tree</code>: snapshot object containing:</p>\n<ul>\n<li><p><code>datasourceName</code> (string): Name of the data source.</p>\n</li>\n<li><p><code>datasourceType</code> (string): Type of the data source.</p>\n</li>\n<li><p><code>schemas</code>: List of schemas, each with:</p>\n<ul>\n<li><p><code>schemaName</code> (string): Name of the schema.</p>\n</li>\n<li><p><code>tables</code>: List of tables in the schema, each with:</p>\n<ul>\n<li><p><code>tableName</code> (string): Name of the table.</p>\n</li>\n<li><p><code>columns</code>: List of columns in the table, each with:</p>\n<ul>\n<li><p><code>columnName</code> (string): Name of the column.</p>\n</li>\n<li><p><code>columnDataType</code> (string): Data type of the column.</p>\n</li>\n<li><p><code>filterType</code> (string): Type of filter applicable (used in row-level policies).</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","tree"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"184ce6e8-deb3-4e38-b3cb-509cf869582c","name":"Get Snapshot - 200 Success","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/tree"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"tree\": {\n        \"datasourceName\": \"bank\",\n        \"datasourceType\": \"Postgres\",\n        \"schemas\": [\n            {\n                \"schemaName\": \"\\\"bank\\\".\\\"core\\\"\",\n                \"tables\": [\n                    {\n                        \"tableName\": \"\\\"branches\\\"\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"\\\"branch_id\\\"\",\n                                \"columnDataType\": \"integer\",\n                                \"columnGeneralDataType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"city\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"columnGeneralDataType\": \"text\"\n                            },\n                            {\n                                \"columnName\": \"\\\"country\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"columnGeneralDataType\": \"text\"\n                            }\n                        ]\n                    },\n                    {\n                        \"tableName\": \"\\\"accounts\\\"\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"\\\"account_id\\\"\",\n                                \"columnDataType\": \"bigint\",\n                                \"columnGeneralDataType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"branch_id\\\"\",\n                                \"columnDataType\": \"integer\",\n                                \"columnGeneralDataType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"holder_first_name\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"columnGeneralDataType\": \"text\"\n                            },\n                            {\n                                \"columnName\": \"\\\"holder_last_name\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"columnGeneralDataType\": \"text\"\n                            },\n                            {\n                                \"columnName\": \"\\\"income\\\"\",\n                                \"columnDataType\": \"integer\",\n                                \"columnGeneralDataType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"is_business\\\"\",\n                                \"columnDataType\": \"boolean\",\n                                \"columnGeneralDataType\": \"boolean\"\n                            }\n                        ]\n                    },\n                    {\n                        \"tableName\": \"\\\"transactions\\\"\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"\\\"transaction_id\\\"\",\n                                \"columnDataType\": \"bigint\",\n                                \"columnGeneralDataType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"account_id\\\"\",\n                                \"columnDataType\": \"bigint\",\n                                \"columnGeneralDataType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"amount\\\"\",\n                                \"columnDataType\": \"numeric\",\n                                \"columnGeneralDataType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"transaction_datetime\\\"\",\n                                \"columnDataType\": \"timestamp without time zone\",\n                                \"columnGeneralDataType\": \"timestamp\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    }\n}"}],"_postman_id":"dfd79214-d411-49b0-898e-19c21d065400"},{"name":"Get Snapshot Status","id":"b1c0ec74-af22-46dd-aa17-9b1e4aa64539","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/snapshot","description":"<p>Returns the status of the last snapshot of a datasource.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>lastRefreshStatus</code></strong> (string): <code>init</code>, <code>indexing</code>, <code>completed</code>, <code>failed</code> or <code>cancelled</code>.</li>\n<li><strong><code>failureReason</code></strong> (string): Why it failed or stopped, if it did.</li>\n<li><strong><code>lastRefreshTrigger</code></strong> (string): When the last snapshot started.</li>\n<li><strong><code>snapshotTimeStamp</code></strong> (string): When the last successful snapshot finished.</li>\n<li><strong><code>snapshotType</code></strong> (string): The type of the last <strong>successful</strong> snapshot: <code>schema_only</code> or <code>standard</code>.</li>\n<li><strong><code>snapshotScanType</code></strong> (string): The type of the last snapshot <strong>run</strong>.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","snapshot"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"6985a9b8-6f33-4278-979a-896142579e4e","name":"Get Snapshot Status - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/snapshot"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"lastRefreshTrigger\": \"2026-10-29T23:49:01.741127Z\",\n    \"lastRefreshStatus\": \"completed\",\n    \"snapshotTimeStamp\": \"2026-10-29T23:49:17.427139Z\",\n    \"snapshotType\": \"standard\",\n    \"snapshotScanType\": \"standard\"\n}"},{"id":"4779e2f9-41ee-40c0-b142-d4138e6e167f","name":"Get Snapshot Status - 200 Cancelled","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/snapshot"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"lastRefreshTrigger\": \"2026-10-30T01:08:38.865491Z\",\n    \"lastRefreshStatus\": \"cancelled\",\n    \"failureReason\": \"Snapshot refresh was manually cancelled\",\n    \"snapshotTimeStamp\": \"2026-10-29T23:49:17.427139Z\",\n    \"snapshotType\": \"standard\",\n    \"snapshotScanType\": \"schema_only\"\n}"}],"_postman_id":"b1c0ec74-af22-46dd-aa17-9b1e4aa64539"},{"name":"Stream Snapshot Progress","id":"f0f515da-adae-4968-883a-10a233bc6b00","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/snapshot/progress","description":"<p>Streams the progress of a snapshot refresh (Server-Sent Events).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response is a stream (<code>text/event-stream</code>). Each <code>progress</code> event has a JSON object: the key is a schema, the value is the percent done (0-100). The stream ends when all schemas reach 100.</p>\n<p>💡 To see the status without a stream, use <strong>Get Snapshot Status</strong>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","snapshot","progress"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"f3154382-c4b4-4ee9-bc73-a21ea304b44a","name":"Get Snapshot Status - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/snapshot/progress"},"status":"OK","code":200,"_postman_previewlanguage":"text","header":[{"key":"Content-Type","value":"text/event-stream"}],"cookie":[],"responseTime":null,"body":"event: progress\ndata: {\"`my-project`.`clinic`\":100,\"`my-project`.`retail`\":25,\"`my-project`.`finance`\":0}\n\nevent: progress\ndata: {\"`my-project`.`clinic`\":100,\"`my-project`.`retail`\":50,\"`my-project`.`finance`\":0}\n\nevent: progress\ndata: {\"`my-project`.`clinic`\":100,\"`my-project`.`retail`\":100,\"`my-project`.`finance`\":100}\n\n"}],"_postman_id":"f0f515da-adae-4968-883a-10a233bc6b00"},{"name":"Refresh Snapshot","id":"5f1c4036-9b11-4942-9103-18c7244fff0a","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/snapshot?profile=true","description":"<p>Starts a new snapshot of the datasource structure. It runs in the background.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"query-parameters\">Query Parameters</h3>\n<ul>\n<li><strong><code>profile</code></strong> (boolean, default <code>true</code>):<ul>\n<li><code>true</code>: <strong>Standard</strong> snapshot (the platform name). PVML reads the structure and data samples. Needed for Differential Privacy and the PII scan.</li>\n<li><code>false</code>: <strong>Schema-only</strong> snapshot. Only tables and columns, no data samples. Faster.</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body. Follow the progress with <strong>Stream Snapshot Progress</strong> or <strong>Get Snapshot Status</strong>.</p>\n<p>⚠️ Only one snapshot can run at a time.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","snapshot"],"host":["platform","pvml","com"],"query":[{"key":"profile","value":"true"}],"variable":[]}},"response":[{"id":"1d3f6a81-7b8a-4320-af87-2c493341eff4","name":"Refresh Snapshot - 200 Success","originalRequest":{"method":"POST","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/snapshot?profile=true","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","snapshot"],"query":[{"key":"profile","value":"true"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"\"\""}],"_postman_id":"5f1c4036-9b11-4942-9103-18c7244fff0a"},{"name":"Stop Snapshot","id":"07a008a6-b424-4962-a223-df1a160bb229","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/snapshot/stop","description":"<p>Stops a snapshot that is running. The previous snapshot stays in use.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>After stopping, <strong>Get Snapshot Status</strong> shows <code>cancelled</code>.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","snapshot","stop"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"213e52df-7277-49bc-8098-c3a73794a72c","name":"Stop Snapshot - 200 OK","originalRequest":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/snapshot/stop"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"07a008a6-b424-4962-a223-df1a160bb229"}],"id":"1810f78f-54ad-4f5f-ae68-2147356e553d","description":"<p>Once the data source is connected, a snapshot of the data schema (the structure of tables and columns) will be pulled.</p>\n<p>You can choose between:</p>\n<ol>\n<li><p><strong>Standard snapshot:</strong> reads the structure and data samples. Needed for <strong>Differential Privacy</strong> and the <strong>PII scan</strong>.</p>\n</li>\n<li><p><strong>Schema-only snapshot:</strong> reads only schemas, tables and columns, no data samples. Faster, but Differential Privacy and the PII scan are not available.</p>\n</li>\n</ol>\n<p>💡 <strong>Names in the API:</strong> in <strong>Refresh Snapshot</strong>, <code>profile=true</code> is Standard and <code>profile=false</code> is Schema-only. <strong>Get Snapshot Status</strong> returns <code>standard</code> or <code>schema_only</code>.</p>\n","_postman_id":"1810f78f-54ad-4f5f-ae68-2147356e553d","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Guide: Relations and identifiers","item":[],"id":"90464d7f-7d80-46f3-a1dc-3e9154f649b9","description":"<p><strong>Relations</strong> and <strong>identifiers</strong> are metadata that you add to a datasource. They are not in your database itself. PVML uses them to apply your policies to related tables. AI clients that use a VDB through MCP can also read them.</p>\n<h3 id=\"relations-how-tables-connect\">Relations: how tables connect</h3>\n<p>A relation says: \"this column in table A points to that column in table B\". It is like a foreign key.</p>\n<p>Example from the bank tables:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Referencing table (has the pointer)</th>\n<th>Column</th>\n<th>Referenced table (is pointed to)</th>\n<th>Column</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>accounts</code></td>\n<td><code>branch_id</code></td>\n<td><code>branches</code></td>\n<td><code>branch_id</code></td>\n</tr>\n<tr>\n<td><code>transactions</code></td>\n<td><code>account_id</code></td>\n<td><code>accounts</code></td>\n<td><code>account_id</code></td>\n</tr>\n</tbody>\n</table>\n</div><p>💡 <strong>Which way?</strong> The <strong>referencing</strong> table is the one with many rows per match (many accounts per branch). The <strong>referenced</strong> table is the one with one row per key (one branch per <code>branch_id</code>).</p>\n<p><strong>What relations are used for:</strong></p>\n<ol>\n<li><p><strong>Row filters on related tables.</strong> A row policy on <code>branches.city</code> can filter <code>accounts</code> and <code>transactions</code> too. PVML follows the relations, even across several steps. See <strong>Guide: Row-level security (RLS)</strong>, Step 3.</p>\n</li>\n<li><p><strong>Differential Privacy.</strong> DP needs a path from each table to the identifier. Without it, DP blocks the query.</p>\n</li>\n<li><p><strong>Correct joins in AI clients.</strong> When an AI client describes a VDB through MCP, it gets the relations between the tables it can see. They tell it how to join the tables.</p>\n</li>\n</ol>\n<p>Set them with <strong>Update Relations</strong>. One call sets all relations of one schema. Send an empty list to remove them. In the PVML UI, <strong>Pull From Source</strong> reads the foreign keys from your database for you.</p>\n<p>[SCREENSHOT: relations.png]</p>\n<h3 id=\"identifiers-who-is-the-person\">Identifiers: who is the \"person\"</h3>\n<p>An identifier is the column that identifies the person (or entity) you want to protect, for example <code>accounts.account_id</code> or <code>customers.customer_id</code>.</p>\n<p><strong>What identifiers are used for:</strong></p>\n<ul>\n<li><p><strong>Differential Privacy</strong> protects each identifier. A query must look at many identifiers at once. For example, with DP on the bank schema:</p>\n<ul>\n<li><p><code>SELECT COUNT(\\\\*) FROM accounts</code> returns a value close to the truth, for example <code>4999.3</code> instead of <code>5000</code>.</p>\n</li>\n<li><p><code>SELECT country, AVG(income) FROM accounts GROUP BY country</code> works, with small noise.</p>\n</li>\n<li><p><code>SELECT \\\\* FROM accounts</code> is blocked: only aggregate queries are allowed.</p>\n</li>\n<li><p>A query about one account (<code>WHERE account_id = 410</code>) returns no result.</p>\n</li>\n</ul>\n</li>\n<li><p>Tables without a relation path to an identifier can not be queried under DP. The logic: if PVML can not tell which person a row belongs to, it can not protect that person, so it blocks the query. Add the relations.</p>\n</li>\n</ul>\n<p>Set identifiers with <strong>Update Identifiers</strong>, one schema per call.</p>\n<p>⚠️ DP also needs a <strong>Standard</strong> snapshot of the datasource, not Schema-only (see <strong>Refresh Snapshot</strong>).</p>\n","_postman_id":"90464d7f-7d80-46f3-a1dc-3e9154f649b9","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Identifiers","item":[{"name":"Get Identifiers","id":"4f77d84d-e170-4f63-851d-aa5426f25ed8","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/identifiers","description":"<p>This endpoint retrieves the identifiers for a specific datasource within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>datasource_id</code> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response will be <strong>schemaIdentifiers (array of objects)</strong>: A list of schema identifier objects, each representing an identifier.</p>\n<ul>\n<li><p><strong>workspaceId (string)</strong>: The unique ID of the workspace containing the datasource.</p>\n</li>\n<li><p><strong>datasourceId (string)</strong>: The unique ID of the datasource associated with the schema.</p>\n</li>\n<li><p><strong>schema (string)</strong>: The schema path in the datasource.</p>\n</li>\n<li><p><strong>identifiers (array of objects)</strong>: A list of column identifiers within the schema.</p>\n<ul>\n<li><p><strong>tableName (string)</strong>: The name of a table in the schema.</p>\n</li>\n<li><p><strong>columnNames (array of strings)</strong>: A list of column names within the table.</p>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","identifiers"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"57d40e62-a9ce-45f5-aa74-dee3bffe2b5b","name":"Get Identifiers - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/identifiers"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"schemaIdentifiers\": [\n        {\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"schema\": \"`my-project`.`clinic`\",\n            \"identifiers\": [\n                {\n                    \"tableName\": \"`doctors`\",\n                    \"columnNames\": [\n                        \"`id`\"\n                    ]\n                }\n            ]\n        }\n    ]\n}"}],"_postman_id":"4f77d84d-e170-4f63-851d-aa5426f25ed8"},{"name":"Update Identifiers","id":"6589af02-4af4-4f63-b547-da3b6296f8dc","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"schema\": \"`my-project`.`clinic`\",\n    \"identifiers\": [\n        {\n            \"tableName\": \"`doctors`\",\n            \"columnNames\": [\n                \"`id`\"\n            ]\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/identifiers","description":"<p>This endpoint updates the identifiers for a specific datasource within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>datasource_id</code> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><code>schema</code> (string): The schema path.</p>\n</li>\n<li><p><code>identifiers</code> (array): An array of objects containing <code>tableName</code> (string) and <code>columnNames</code> (array of strings).</p>\n</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Upon successful update, the server returns a status code of 200 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","identifiers"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"ea040946-e8c9-47a2-ba56-97e46f955da2","name":"Update Identifiers - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"schema\": \"`my-project`.`clinic`\",\n    \"identifiers\": [\n        {\n            \"tableName\": \"`doctors`\",\n            \"columnNames\": [\n                \"`id`\"\n            ]\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/identifiers"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"schema\": \"`my-project`.`clinic`\",\n    \"identifiers\": [\n        {\n            \"tableName\": \"`doctors`\",\n            \"columnNames\": [\n                \"`id`\"\n            ]\n        }\n    ]\n}"}],"_postman_id":"6589af02-4af4-4f63-b547-da3b6296f8dc"}],"id":"d9d5fe9e-2dba-430b-9bea-3c5528ba8193","description":"<p>💡 <strong>What is an identifier?</strong></p>\n<p>An identifier is the entity you're aiming to keep private in the analysis.</p>\n<p>This field specifies the column whose raw data - potentially across multiple rows - you wish to protect when applying a <strong>differential privacy</strong>.</p>\n<p>Note: an identifier <strong>does not have to be PII</strong> (Personally Identifiable Information) because the concept of privacy here is broader than just personal information.</p>\n<p>The identifier is simply the element (like a person, a transaction, or an event) whose presence or absence in the data should not significantly change the query result.</p>\n<p>💡 <strong>Why can't I just mask the identifier or the PII?</strong></p>\n<p>Masking or removing identifiers or PII isn’t enough because other information in the dataset (e.g., role, education, city) can still indirectly identify individuals when combined or cross referenced with external data (e.g., LinkedIn profile).</p>\n<img src=\"https://content.pstmn.io/2ca34edb-28be-4002-bde0-10c7d7f3fba2/QW5vbnltaXphdGlvbiAtIFY0LnBuZw==\" alt=\"Re-identification%20of%20Sensitive%20Data%20Through%20Cross-Referencing%20External%20Information:%20Why%20Masking%20the%20Identifier/PII%20isn't%20Good%20Enough\" />\n\n<p>📘 See <strong>Guide: Relations and identifiers</strong>.</p>\n","_postman_id":"d9d5fe9e-2dba-430b-9bea-3c5528ba8193","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Descriptions","item":[{"name":"Get Descriptions","id":"fa25a587-5fc0-4592-8083-e49945a8f89e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions","description":"<p>Returns the descriptions of schemas, tables and columns of a datasource.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>descriptions</code></strong> (array). Each item has:<ul>\n<li><strong><code>schemaName</code></strong> (string)</li>\n<li><strong><code>tableName</code></strong> (string): Empty for a schema description.</li>\n<li><strong><code>columnName</code></strong> (string): Empty for a schema or table description.</li>\n<li><strong><code>description</code></strong> (string)</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","descriptions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"7d64c14d-a15d-4ac5-8892-fcdcfb4f2ea7","name":"Get Descriptions - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"descriptions\": [\n        {\n            \"schemaName\": \"`my-project`.`clinic`\",\n            \"tableName\": \"`doctors`\",\n            \"columnName\": \"`name`\",\n            \"description\": \"full name of the doctor\"\n        },\n        {\n            \"schemaName\": \"`my-project`.`clinic`\",\n            \"tableName\": \"`patients`\",\n            \"columnName\": \"`doctor`\",\n            \"description\": \"id of the patient's doctor\"\n        }\n    ]\n}"}],"_postman_id":"fa25a587-5fc0-4592-8083-e49945a8f89e"},{"name":"Get Snapshot with Descriptions","id":"4ee94485-0237-49f9-a255-f05654b36b59","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/tree?enrich=true&pii=false","description":"<p>This endpoint retrieves the snapshot (tree structure) with descriptions.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>datasource_id</code></strong> (string): The unique identifier for the datasource.</p>\n</li>\n<li><p><strong><code>enrich</code></strong> (boolean, query parameter): When set to <code>true</code>, includes descriptions in the tree response.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p><code>tree</code>: snapshot object containing:</p>\n<ul>\n<li><p><code>datasourceName</code> (string): Name of the data source.</p>\n</li>\n<li><p><code>datasourceType</code> (string): Type of the data source.</p>\n</li>\n<li><p><code>schemas</code>: List of schemas, each with:</p>\n<ul>\n<li><p><code>schemaName</code> (string): Name of the schema.</p>\n</li>\n<li><p><code>tables</code>: List of tables in the schema, each with:</p>\n<ul>\n<li><p><code>tableName</code> (string): Name of the table.</p>\n</li>\n<li><p><code>columns</code>: List of columns in the table, each with:</p>\n<ul>\n<li><p><code>columnName</code> (string): Name of the column.</p>\n</li>\n<li><p><code>columnDataType</code> (string): Data type of the column.</p>\n</li>\n<li><p><code>filterType</code> (string): Type of filter applicable (used in row-level policies).</p>\n</li>\n<li><p><strong><code>columnDescription</code></strong> (string): The description of the column.</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","tree"],"host":["platform","pvml","com"],"query":[{"key":"enrich","value":"true"},{"key":"pii","value":"false"}],"variable":[]}},"response":[{"id":"531e3f09-3ec0-4fad-a273-b3c2259430c6","name":"Get Snapshot with Descriptions - 200 Success","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/tree?enrich=true&pii=false","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","tree"],"query":[{"key":"enrich","value":"true"},{"key":"pii","value":"false"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"tree\": {\n        \"datasourceName\": \"bank\",\n        \"datasourceType\": \"Postgres\",\n        \"schemas\": [\n            {\n                \"schemaName\": \"\\\"bank\\\".\\\"core\\\"\",\n                \"tables\": [\n                    {\n                        \"tableName\": \"\\\"branches\\\"\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"\\\"branch_id\\\"\",\n                                \"columnDataType\": \"integer\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"city\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"filterType\": \"text\",\n                                \"columnDescription\": \"City where the branch is located\"\n                            },\n                            {\n                                \"columnName\": \"\\\"country\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"filterType\": \"text\"\n                            }\n                        ]\n                    },\n                    {\n                        \"tableName\": \"\\\"accounts\\\"\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"\\\"account_id\\\"\",\n                                \"columnDataType\": \"bigint\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"branch_id\\\"\",\n                                \"columnDataType\": \"integer\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"holder_first_name\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"filterType\": \"text\"\n                            },\n                            {\n                                \"columnName\": \"\\\"holder_last_name\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"filterType\": \"text\"\n                            },\n                            {\n                                \"columnName\": \"\\\"income\\\"\",\n                                \"columnDataType\": \"integer\",\n                                \"filterType\": \"numeric\",\n                                \"columnDescription\": \"Yearly income of the account holder, in EUR\"\n                            },\n                            {\n                                \"columnName\": \"\\\"is_business\\\"\",\n                                \"columnDataType\": \"boolean\",\n                                \"filterType\": \"boolean\"\n                            }\n                        ]\n                    },\n                    {\n                        \"tableName\": \"\\\"transactions\\\"\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"\\\"transaction_id\\\"\",\n                                \"columnDataType\": \"bigint\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"account_id\\\"\",\n                                \"columnDataType\": \"bigint\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"amount\\\"\",\n                                \"columnDataType\": \"numeric\",\n                                \"filterType\": \"numeric\",\n                                \"columnDescription\": \"Transaction amount; negative for withdrawals\"\n                            },\n                            {\n                                \"columnName\": \"\\\"transaction_datetime\\\"\",\n                                \"columnDataType\": \"timestamp without time zone\",\n                                \"filterType\": \"timestamp\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    }\n}"}],"_postman_id":"4ee94485-0237-49f9-a255-f05654b36b59"},{"name":"Update Descriptions","id":"2661ce44-a496-45aa-95ea-542b51d3cd3f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"toAdd\": [\n        {\n            \"schemaName\": \"`my-project`.`clinic`\",\n            \"tableName\": \"`doctors`\",\n            \"columnName\": \"`name`\",\n            \"description\": \"full name of the doctor\"\n        },\n        {\n            \"schemaName\": \"`my-project`.`clinic`\",\n            \"tableName\": \"`patients`\",\n            \"columnName\": \"`doctor`\",\n            \"description\": \"id of the patient's doctor\"\n        }\n    ],\n    \"toRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions","description":"<p>Adds, changes or removes descriptions of schemas, tables and columns.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>toAdd</code></strong> (array): Descriptions to add or replace. Each item has <code>schemaName</code>, <code>tableName</code>, <code>columnName</code> and <code>description</code>. For a table description, leave out <code>columnName</code>. For a schema description, leave out <code>tableName</code> and <code>columnName</code>.</li>\n<li><strong><code>toRemove</code></strong> (array): Descriptions to delete. Each item has <code>schemaName</code>, <code>tableName</code> and <code>columnName</code> (no <code>description</code>).</li>\n</ul>\n<p>Use full names with the quote style of your database.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The request body, as saved.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","descriptions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"206f691d-b441-435e-b1f0-9206d890eb04","name":"Update Descriptions - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"toAdd\": [\n        {\n            \"schemaName\": \"`my-project`.`clinic`\",\n            \"tableName\": \"`doctors`\",\n            \"columnName\": \"`name`\",\n            \"description\": \"full name of the doctor\"\n        },\n        {\n            \"schemaName\": \"`my-project`.`clinic`\",\n            \"tableName\": \"`patients`\",\n            \"columnName\": \"`doctor`\",\n            \"description\": \"id of the patient's doctor\"\n        }\n    ],\n    \"toRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"toAdd\": [\n        {\n            \"schemaName\": \"`my-project`.`clinic`\",\n            \"tableName\": \"`doctors`\",\n            \"columnName\": \"`name`\",\n            \"description\": \"full name of the doctor\"\n        },\n        {\n            \"schemaName\": \"`my-project`.`clinic`\",\n            \"tableName\": \"`patients`\",\n            \"columnName\": \"`doctor`\",\n            \"description\": \"id of the patient's doctor\"\n        }\n    ],\n    \"toRemove\": []\n}"}],"_postman_id":"2661ce44-a496-45aa-95ea-542b51d3cd3f"},{"name":"Scan Descriptions","id":"89233ef0-0948-4a20-9440-13b6612c5f66","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"toScan\": [\n        {\n            \"schemaName\": \"`my-project`.`core`\",\n            \"tableName\": \"`accounts`\",\n            \"columnName\": \"`income`\"\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions/scan","description":"<p>Writes descriptions for chosen columns, tables or schemas with the workspace System LLM. PVML saves them right away.</p>\n<p>💡 This uses the workspace <strong>System LLM</strong> (see <strong>LLMs</strong>).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>toScan</code></strong> (array, required): The columns to describe. Each item has <code>schemaName</code>, <code>tableName</code> and <code>columnName</code> (full names with quotes).</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200. The scan runs in the background. Check it with <strong>Get Descriptions Scan Status</strong>, then read the result with <strong>Get Descriptions</strong>.</p>\n<p>Example result for <code>accounts.income</code>: <em>\"Annual income amount for the account, stored as an integer value (e.g., 25000, 52000)\"</em>.</p>\n<p>💡 Generated descriptions are a good start. Review them: you know your business better.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","descriptions","scan"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"98d98222-d1b1-4ce5-a995-58207c6d3189","name":"Scan Descriptions - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"toScan\": [\n        {\n            \"schemaName\": \"`my-project`.`core`\",\n            \"tableName\": \"`accounts`\",\n            \"columnName\": \"`income`\"\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions/scan"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"89233ef0-0948-4a20-9440-13b6612c5f66"},{"name":"Scan All Descriptions","id":"2d8254d8-ae02-4f56-bbd2-010227ece2b9","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions/scan/full","description":"<p>Writes descriptions for the whole datasource with the workspace System LLM. For a large database this can take a while.</p>\n<p>💡 This uses the workspace <strong>System LLM</strong> (see <strong>LLMs</strong>).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body. The scan runs in the background.</p>\n<p>⚠️ Only one description scan can run at a time.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","descriptions","scan","full"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"0c169ee0-ebcf-430f-b115-aa12d97cd371","name":"Scan All Descriptions - 200 OK","originalRequest":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions/scan/full"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"2d8254d8-ae02-4f56-bbd2-010227ece2b9"},{"name":"Stop Descriptions Scan","id":"0968e23e-e671-4c06-9c9b-8fe850a38205","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions/scan/stop","description":"<p>Stops a running description scan. Descriptions that are already saved stay.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","descriptions","scan","stop"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"7da0b9e1-46ff-4557-975c-230c3f757e7f","name":"Stop Descriptions Scan - 200 OK","originalRequest":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions/scan/stop"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"0968e23e-e671-4c06-9c9b-8fe850a38205"},{"name":"Get Descriptions Scan Status","id":"a6b74618-9dca-4f81-9b98-ceb6534ef579","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions/scan-status","description":"<p>Returns the status of the last description scan.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>lastRefreshStatus</code></strong> (string): <code>init</code> (never ran), <code>indexing</code>, <code>completed</code>, <code>failed</code> or <code>cancelled</code>.</li>\n<li><strong><code>lastRefreshTrigger</code></strong> (string): When it started.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","descriptions","scan-status"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"176c675c-8396-44c3-96f3-06a46123f5ec","name":"Get Descriptions Scan Status - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions/scan-status"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"lastRefreshTrigger\": \"2026-10-30T01:06:57.442421Z\",\n    \"lastRefreshStatus\": \"completed\"\n}"}],"_postman_id":"a6b74618-9dca-4f81-9b98-ceb6534ef579"},{"name":"Stream Descriptions Scan Progress","id":"bf0d6375-ef15-47e9-8e1a-2f7944857f41","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/descriptions/scan/progress","description":"<p>Streams the progress of a description scan (Server-Sent Events).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A <code>text/event-stream</code> with these events: <code>progress</code>, <code>progress_error</code> (one item failed, the scan goes on), <code>done</code> and <code>error</code>.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","descriptions","scan","progress"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[],"_postman_id":"bf0d6375-ef15-47e9-8e1a-2f7944857f41"}],"id":"a90b7d08-20c1-4c3a-b333-3172cc13b32a","description":"<p>💡 <strong>Why add descriptions?</strong></p>\n<p>A description explains in plain words what a schema, table or column means. For example: the column <code>amt_2</code> is \"annual income of the account holder, in GBP\".</p>\n<p>Descriptions help people and AI clients understand your data:</p>\n<ul>\n<li>In the platform, users see them next to the tables and columns.</li>\n<li>When an AI client describes a VDB through MCP, it gets the descriptions of the tables and columns it can see. This helps it pick the right column, for example <code>amt_2</code> and not <code>amt_1</code>.</li>\n</ul>\n<p>Descriptions are text only. They do not change your data or your policies.</p>\n","_postman_id":"a90b7d08-20c1-4c3a-b333-3172cc13b32a","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Relations","item":[{"name":"Get Relations","id":"801cd1e7-9112-48c8-9472-0bced91254d6","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/relations","description":"<p>This endpoint retrieves the metadata relations for a specific datasource within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>datasource_id</code> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response will be <strong>schemaRelations</strong>: a list of schema <strong>relations</strong> objects, each containing details about how tables and columns are related within a schema.</p>\n<ul>\n<li><p><strong>workspaceId (string)</strong>: The ID of the workspace where the datasource resides.</p>\n</li>\n<li><p><strong>datasourceId (string)</strong>: The ID of the datasource containing the schema.</p>\n</li>\n<li><p><strong>schema (string)</strong>: The schema path within the datasource.</p>\n</li>\n<li><p><strong>relations (array of objects)</strong>: A list of table-to-table relationships within the schema.</p>\n<ul>\n<li><p><strong>referencingTable (string)</strong>: The name of the table that references another table.</p>\n</li>\n<li><p><strong>referencedTable (string)</strong>: The name of the table being referenced.</p>\n</li>\n<li><p><strong>columnsReferences (array of objects)</strong>: A list of column-to-column references between the tables.</p>\n<ul>\n<li><p><strong>referencingColumn (string)</strong>: The column in the referencing table.</p>\n</li>\n<li><p><strong>referencedColumn (string)</strong>: The column in the referenced table.</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","relations"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"ffbcc144-2d9e-4f3a-81a3-a162665a2514","name":"Get Relations - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/relations"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"schemaRelations\": [\n        {\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"schema\": \"`my-project`.`clinic`\",\n            \"relations\": [\n                {\n                    \"referencingTable\": \"`patients`\",\n                    \"referencedTable\": \"`doctors`\",\n                    \"columnsReferences\": [\n                        {\n                            \"referencingColumn\": \"`doctor`\",\n                            \"referencedColumn\": \"`id`\"\n                        }\n                    ]\n                }\n            ]\n        }\n    ]\n}"}],"_postman_id":"801cd1e7-9112-48c8-9472-0bced91254d6"},{"name":"Update Relations","id":"12564356-8544-44e5-a226-abe477436fc4","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"schema\": \"`my-project`.`clinic`\",\n    \"relations\": [\n        {\n            \"referencingTable\": \"`patients`\",\n            \"referencedTable\": \"`doctors`\",\n            \"columnsReferences\": [\n                {\n                    \"referencingColumn\": \"`doctor`\",\n                    \"referencedColumn\": \"`id`\"\n                }\n            ]\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/relations","description":"<p>This endpoint allows you to send an updated list of relations for a specific datasource within a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>datasource_id</code> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><code>schema</code> (string): The schema path in the datasource.</p>\n</li>\n<li><p><code>relations</code> (array):</p>\n<ul>\n<li><p><code>referencingTable</code> (string): The referencing table in the relation.</p>\n</li>\n<li><p><code>referencedTable</code> (string): The referenced table in the relation.</p>\n</li>\n<li><p><code>columnsReferences</code> (array):</p>\n<ul>\n<li><p><code>referencingColumn</code> (string): The referencing column in the relation.</p>\n</li>\n<li><p><code>referencedColumn</code> (string): The referenced column in the relation.</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response will show the resulting relations:</p>\n<ul>\n<li><p><code>schema</code> (string): The schema path in the datasource.</p>\n</li>\n<li><p><code>relations</code> (array):</p>\n<ul>\n<li><p><code>referencingTable</code> (string): The referencing table in the relation.</p>\n</li>\n<li><p><code>referencedTable</code> (string): The referenced table in the relation.</p>\n</li>\n<li><p><code>columnsReferences</code> (array):</p>\n<ul>\n<li><p><code>referencingColumn</code> (string): The referencing column in the relation.</p>\n</li>\n<li><p><code>referencedColumn</code> (string): The referenced column in the relation.</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","relations"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"0e91fcf4-54ba-4f86-a688-f7f1dea0bcfa","name":"Update Relations - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"schema\": \"`my-project`.`clinic`\",\n    \"relations\": [\n        {\n            \"referencingTable\": \"`patients`\",\n            \"referencedTable\": \"`doctors`\",\n            \"columnsReferences\": [\n                {\n                    \"referencingColumn\": \"`doctor`\",\n                    \"referencedColumn\": \"`id`\"\n                }\n            ]\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/relations"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"schema\": \"`my-project`.`clinic`\",\n    \"relations\": [\n        {\n            \"referencingTable\": \"`patients`\",\n            \"referencedTable\": \"`doctors`\",\n            \"columnsReferences\": [\n                {\n                    \"referencingColumn\": \"`doctor`\",\n                    \"referencedColumn\": \"`id`\"\n                }\n            ]\n        }\n    ]\n}"}],"_postman_id":"12564356-8544-44e5-a226-abe477436fc4"},{"name":"Pull Relations From Source","id":"4e360d4c-e207-43df-8dc3-1da99eafe85e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/relations/pull","description":"<p>Reads the foreign keys from your database and saves them as relations. Relations you added before are kept.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>schemaRelations</code></strong> (array): All saved relations, after the pull.</li>\n</ul>\n<p>💡 This is the fastest and most exact way to get relations. Run it first, then add missing ones by hand or with <strong>Suggest Relations</strong>.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","relations","pull"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"ea7c8f7b-bfb2-4369-a058-72b6e7afb641","name":"Pull Relations From Source - 200 OK","originalRequest":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/relations/pull"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"schemaRelations\": [\n        {\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"schema\": \"`my-project`.`core`\",\n            \"relations\": [\n                {\n                    \"referencingTable\": \"`accounts`\",\n                    \"referencedTable\": \"`branches`\",\n                    \"columnsReferences\": [\n                        {\n                            \"referencingColumn\": \"`branch_id`\",\n                            \"referencedColumn\": \"`branch_id`\"\n                        }\n                    ]\n                }\n            ]\n        }\n    ]\n}"}],"_postman_id":"4e360d4c-e207-43df-8dc3-1da99eafe85e"},{"name":"Suggest Relations","id":"34694617-abea-4691-8a01-1fe6e228f095","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/relations/scan","description":"<p>Suggests relations from table and column names, with the workspace System LLM. Useful when your database has no foreign keys (for example BigQuery or a data lake).</p>\n<p>💡 This uses the workspace <strong>System LLM</strong> (see <strong>LLMs</strong>).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>schemaRelations</code></strong> (array): The suggestions.</li>\n</ul>\n<p>⚠️ <strong>Suggestions are not saved.</strong> Check them, then save the ones you want with <strong>Update Relations</strong>.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","relations","scan"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"41666359-f4a1-4d06-b673-e8378260632f","name":"Suggest Relations - 200 OK","originalRequest":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/relations/scan"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"schemaRelations\": [\n        {\n            \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n            \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n            \"schema\": \"`my-project`.`core`\",\n            \"relations\": [\n                {\n                    \"referencingTable\": \"transactions\",\n                    \"referencedTable\": \"accounts\",\n                    \"columnsReferences\": [\n                        {\n                            \"referencingColumn\": \"account_id\",\n                            \"referencedColumn\": \"account_id\"\n                        }\n                    ]\n                }\n            ]\n        }\n    ]\n}"}],"_postman_id":"34694617-abea-4691-8a01-1fe6e228f095"},{"name":"Find New Source Relations","id":"9335996d-970c-4c3b-9cb5-ab62461495c6","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/relations/diff-scan","description":"<p>Compares the foreign keys in your database with the saved relations, and returns the ones that are not saved yet. Nothing is saved.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>schemaRelations</code></strong> (array): Per schema, the foreign keys that are missing in PVML. Empty lists mean you are up to date.</li>\n</ul>\n<p>💡 Run it after a database change. If it finds something, use <strong>Pull From Source</strong> to save it.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","metadata","relations","diff-scan"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"fb330531-6d31-4606-bc2f-97e6c3ccf399","name":"Find New Source Relations - 200 OK","originalRequest":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/metadata/relations/diff-scan"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"schemaRelations\": [\n        {\n            \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n            \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n            \"schema\": \"`my-project`.`core`\",\n            \"relations\": []\n        }\n    ]\n}"}],"_postman_id":"9335996d-970c-4c3b-9cb5-ab62461495c6"}],"id":"6f9daf02-b539-4ccd-b92e-16775c571162","description":"<p>Relations tell PVML how tables are linked, like foreign keys. For example: <code>accounts.branch_id</code> points to <code>branches.branch_id</code>.</p>\n<p>PVML uses relations to:</p>\n<ol>\n<li><strong>Apply row filters to related tables.</strong> A filter on branches can also filter accounts and transactions.</li>\n<li><strong>Run Differential Privacy.</strong> DP follows the relations from each table to the identifier.</li>\n<li><strong>Show AI clients how to join tables.</strong> When an AI client describes a VDB through MCP, it gets the relations between the tables it can see.</li>\n</ol>\n<p>📘 See <strong>Guide: Relations and identifiers</strong> for when and how to use relations.</p>\n","_postman_id":"6f9daf02-b539-4ccd-b92e-16775c571162","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"PII Catalog","item":[{"name":"Get Snapshot with PII","id":"47f77b0e-4027-47b1-bc01-a87e3ae1c352","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/tree?enrich=false&pii=true","description":"<p>This endpoint retrieves the datasource snapshot (tree structure) <strong>with PIIs tagged</strong> (<code>\"pii\": true</code>).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>datasource_id</code></strong> (string): The unique identifier for the datasource.</p>\n</li>\n<li><p><strong><code>pii</code></strong> (boolean, query parameter): When set to <code>true</code>, includes PII tag in the tree response.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p><strong><code>tree</code></strong>: snapshot object containing:</p>\n<ul>\n<li><p><strong><code>datasourceName</code></strong> (string): Name of the data source.</p>\n</li>\n<li><p><strong><code>datasourceType</code></strong> (string): Type of the data source.</p>\n</li>\n<li><p><strong><code>schemas</code></strong>: List of schemas, each with:</p>\n<ul>\n<li><p><strong><code>schemaName</code></strong> (string): Name of the schema.</p>\n</li>\n<li><p><strong><code>tables</code></strong>: List of tables in the schema, each with:</p>\n<ul>\n<li><p><strong><code>tableName</code></strong> (string): Name of the table.</p>\n</li>\n<li><p><strong><code>columns</code></strong>: List of columns in the table, each with:</p>\n<ul>\n<li><p><strong><code>columnName</code></strong> (string): Name of the column.</p>\n</li>\n<li><p><strong><code>columnDataType</code></strong> (string): Data type of the column.</p>\n</li>\n<li><p><strong><code>filterType</code></strong> (string): Type of filter applicable (used in row-level policies).</p>\n</li>\n<li><p><strong><code>pii</code></strong> (boolean, query parameter): Appears if the field was tagged as PII (equals <code>true</code>).</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","tree"],"host":["platform","pvml","com"],"query":[{"key":"enrich","value":"false"},{"key":"pii","value":"true"}],"variable":[]}},"response":[{"id":"cbcc17bd-83c1-4717-a57a-dfe9246b6812","name":"Get Snapshot with PII - 200 Success","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/tree?enrich=false&pii=true","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","tree"],"query":[{"key":"enrich","value":"false"},{"key":"pii","value":"true"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"tree\": {\n        \"datasourceName\": \"bank\",\n        \"datasourceType\": \"Postgres\",\n        \"schemas\": [\n            {\n                \"schemaName\": \"\\\"bank\\\".\\\"core\\\"\",\n                \"tables\": [\n                    {\n                        \"tableName\": \"\\\"branches\\\"\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"\\\"branch_id\\\"\",\n                                \"columnDataType\": \"integer\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"city\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"filterType\": \"text\"\n                            },\n                            {\n                                \"columnName\": \"\\\"country\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"filterType\": \"text\"\n                            }\n                        ]\n                    },\n                    {\n                        \"tableName\": \"\\\"accounts\\\"\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"\\\"account_id\\\"\",\n                                \"columnDataType\": \"bigint\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"branch_id\\\"\",\n                                \"columnDataType\": \"integer\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"holder_first_name\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"filterType\": \"text\",\n                                \"pii\": true\n                            },\n                            {\n                                \"columnName\": \"\\\"holder_last_name\\\"\",\n                                \"columnDataType\": \"text\",\n                                \"filterType\": \"text\",\n                                \"pii\": true\n                            },\n                            {\n                                \"columnName\": \"\\\"income\\\"\",\n                                \"columnDataType\": \"integer\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"is_business\\\"\",\n                                \"columnDataType\": \"boolean\",\n                                \"filterType\": \"boolean\"\n                            }\n                        ]\n                    },\n                    {\n                        \"tableName\": \"\\\"transactions\\\"\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"\\\"transaction_id\\\"\",\n                                \"columnDataType\": \"bigint\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"account_id\\\"\",\n                                \"columnDataType\": \"bigint\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"amount\\\"\",\n                                \"columnDataType\": \"numeric\",\n                                \"filterType\": \"numeric\"\n                            },\n                            {\n                                \"columnName\": \"\\\"transaction_datetime\\\"\",\n                                \"columnDataType\": \"timestamp without time zone\",\n                                \"filterType\": \"timestamp\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    }\n}"}],"_postman_id":"47f77b0e-4027-47b1-bc01-a87e3ae1c352"},{"name":"Get PII Catalog","id":"3659dc33-921f-49a1-a75a-c3da3bc27e83","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/snapshot","description":"<p>This endpoint retrieves the PII catalog of a specific datasource in a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>datasource_id</code></strong> (string): The unique identifier for the datasource.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p><strong><code>catalogs</code></strong>: an array containing snapshots of the catalog:</p>\n<ul>\n<li><p><strong><code>changeTime</code></strong> (string, ISO 8601 format): Represents the timestamp when the PII catalog was last modified, creating this version.</p>\n</li>\n<li><p><strong><code>userId</code></strong> (string): The unique ID of the catalog's version editor.</p>\n</li>\n<li><p><strong><code>userEmail</code></strong> (string): The email of the user who edited the catalog, creating the version.</p>\n</li>\n<li><p><strong><code>datasourceId</code></strong> (string): The unique ID of the datasource.</p>\n</li>\n<li><p><strong><code>id</code></strong> (string): The unique id of the catalog snapshot.</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): The unique ID of the workspace containing the datasource.</p>\n</li>\n<li><p><strong><code>tree</code></strong> object containing an array of:</p>\n<ul>\n<li><p><strong><code>datasourceName</code></strong> (string): Name of the data source.</p>\n</li>\n<li><p><strong><code>datasourceType</code></strong> (string): Type of the data source.</p>\n</li>\n<li><p><strong><code>schemas</code></strong>: List of schemas, each with:</p>\n<ul>\n<li><p><strong><code>schemaName</code></strong> (string): Name of the schema.</p>\n</li>\n<li><p><strong><code>tables</code></strong>: List of tables in the schema, each with:</p>\n<ul>\n<li><p><strong><code>tableName</code></strong> (string): Name of the table.</p>\n</li>\n<li><p><strong><code>columns</code></strong>: List of columns in the table, each with:</p>\n<ul>\n<li><p><strong><code>columnName</code></strong> (string): Name of the column.</p>\n</li>\n<li><p><strong><code>columnDataType</code></strong> (string): Data type of the column.</p>\n</li>\n<li><p><strong><code>filterType</code></strong> (string): Type of filter applicable (used in row-level policies).</p>\n</li>\n<li><p><strong><code>pii</code></strong> (boolean, query parameter): Set to <code>true</code> if the field was tagged as PII.</p>\n</li>\n<li><p><strong><code>piiScanIgnore</code></strong> (boolean): Set to <code>true</code> if the field is ignored as PII in future scans.</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<p>💡 The response also has a <strong><code>pagination</code></strong> object: <code>totalRecords</code>, <code>currentPage</code>, <code>totalPages</code>, <code>pageSize</code>, <code>nextPage</code>, <code>prevPage</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","pii","snapshot"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"59496731-e303-474e-8338-fb3f466c8b35","name":"Get PII Catalog - 200 Success","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/snapshot"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"catalogs\": [\n        {\n            \"changeTime\": \"2026-10-01T09:30:00.000Z\",\n            \"userId\": \"8d117eb2-ff29-5364-96b8-6f6f65091ce4\",\n            \"userEmail\": \"jane.doe@example.com\",\n            \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n            \"id\": \"catalog-0001\",\n            \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n            \"tree\": [\n                {\n                    \"schemaName\": \"\\\"bank\\\".\\\"core\\\"\",\n                    \"tables\": [\n                        {\n                            \"tableName\": \"\\\"branches\\\"\",\n                            \"columns\": [\n                                {\n                                    \"columnName\": \"\\\"branch_id\\\"\",\n                                    \"columnDataType\": \"integer\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"city\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"country\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                }\n                            ]\n                        },\n                        {\n                            \"tableName\": \"\\\"accounts\\\"\",\n                            \"columns\": [\n                                {\n                                    \"columnName\": \"\\\"account_id\\\"\",\n                                    \"columnDataType\": \"bigint\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"branch_id\\\"\",\n                                    \"columnDataType\": \"integer\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"holder_first_name\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": true,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"holder_last_name\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": true,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"income\\\"\",\n                                    \"columnDataType\": \"integer\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"is_business\\\"\",\n                                    \"columnDataType\": \"boolean\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                }\n                            ]\n                        },\n                        {\n                            \"tableName\": \"\\\"transactions\\\"\",\n                            \"columns\": [\n                                {\n                                    \"columnName\": \"\\\"transaction_id\\\"\",\n                                    \"columnDataType\": \"bigint\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"account_id\\\"\",\n                                    \"columnDataType\": \"bigint\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"amount\\\"\",\n                                    \"columnDataType\": \"numeric\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"transaction_datetime\\\"\",\n                                    \"columnDataType\": \"timestamp without time zone\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                }\n                            ]\n                        }\n                    ]\n                }\n            ]\n        }\n    ],\n    \"pagination\": null\n}"}],"_postman_id":"3659dc33-921f-49a1-a75a-c3da3bc27e83"},{"name":"Add to Catalog","id":"b029bdcb-4b10-4815-b302-cad326f30a22","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"tree\": [\n        {\n            \"schemaName\": \"\\\"bank\\\".\\\"core\\\"\",\n            \"tables\": [\n                {\n                    \"tableName\": \"\\\"accounts\\\"\",\n                    \"columns\": [\n                        {\n                            \"columnName\": \"\\\"holder_first_name\\\"\",\n                            \"columnDataType\": \"text\",\n                            \"filterType\": \"text\"\n                        },\n                        {\n                            \"columnName\": \"\\\"holder_last_name\\\"\",\n                            \"columnDataType\": \"text\",\n                            \"filterType\": \"text\"\n                        }\n                    ]\n                }\n            ]\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/snapshot","description":"<p>💡 The response also has a <strong><code>pagination</code></strong> object: <code>totalRecords</code>, <code>currentPage</code>, <code>totalPages</code>, <code>pageSize</code>, <code>nextPage</code>, <code>prevPage</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","pii","snapshot"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"6d26c0e7-ecd3-405c-90a6-4e6baaff1b92","name":"Add to Catalog - 200 Success","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"tree\": [\n        {\n            \"schemaName\": \"\\\"bank\\\".\\\"core\\\"\",\n            \"tables\": [\n                {\n                    \"tableName\": \"\\\"accounts\\\"\",\n                    \"columns\": [\n                        {\n                            \"columnName\": \"\\\"holder_first_name\\\"\",\n                            \"columnDataType\": \"text\",\n                            \"filterType\": \"text\"\n                        },\n                        {\n                            \"columnName\": \"\\\"holder_last_name\\\"\",\n                            \"columnDataType\": \"text\",\n                            \"filterType\": \"text\"\n                        }\n                    ]\n                }\n            ]\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/snapshot"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"catalogs\": [\n        {\n            \"changeTime\": \"2026-10-02T11:15:00.000Z\",\n            \"userId\": \"8d117eb2-ff29-5364-96b8-6f6f65091ce4\",\n            \"userEmail\": \"jane.doe@example.com\",\n            \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n            \"id\": \"catalog-0002\",\n            \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n            \"tree\": [\n                {\n                    \"schemaName\": \"\\\"bank\\\".\\\"core\\\"\",\n                    \"tables\": [\n                        {\n                            \"tableName\": \"\\\"branches\\\"\",\n                            \"columns\": [\n                                {\n                                    \"columnName\": \"\\\"branch_id\\\"\",\n                                    \"columnDataType\": \"integer\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"city\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"country\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                }\n                            ]\n                        },\n                        {\n                            \"tableName\": \"\\\"accounts\\\"\",\n                            \"columns\": [\n                                {\n                                    \"columnName\": \"\\\"account_id\\\"\",\n                                    \"columnDataType\": \"bigint\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"branch_id\\\"\",\n                                    \"columnDataType\": \"integer\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"holder_first_name\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": true,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"holder_last_name\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": true,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"income\\\"\",\n                                    \"columnDataType\": \"integer\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"is_business\\\"\",\n                                    \"columnDataType\": \"boolean\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                }\n                            ]\n                        },\n                        {\n                            \"tableName\": \"\\\"transactions\\\"\",\n                            \"columns\": [\n                                {\n                                    \"columnName\": \"\\\"transaction_id\\\"\",\n                                    \"columnDataType\": \"bigint\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"account_id\\\"\",\n                                    \"columnDataType\": \"bigint\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"amount\\\"\",\n                                    \"columnDataType\": \"numeric\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"transaction_datetime\\\"\",\n                                    \"columnDataType\": \"timestamp without time zone\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                }\n                            ]\n                        }\n                    ]\n                }\n            ]\n        },\n        {\n            \"changeTime\": \"2026-10-01T09:30:00.000Z\",\n            \"userId\": \"8d117eb2-ff29-5364-96b8-6f6f65091ce4\",\n            \"userEmail\": \"jane.doe@example.com\",\n            \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n            \"id\": \"catalog-0001\",\n            \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n            \"tree\": [\n                {\n                    \"schemaName\": \"\\\"bank\\\".\\\"core\\\"\",\n                    \"tables\": [\n                        {\n                            \"tableName\": \"\\\"branches\\\"\",\n                            \"columns\": [\n                                {\n                                    \"columnName\": \"\\\"branch_id\\\"\",\n                                    \"columnDataType\": \"integer\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"city\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"country\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                }\n                            ]\n                        },\n                        {\n                            \"tableName\": \"\\\"accounts\\\"\",\n                            \"columns\": [\n                                {\n                                    \"columnName\": \"\\\"account_id\\\"\",\n                                    \"columnDataType\": \"bigint\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"branch_id\\\"\",\n                                    \"columnDataType\": \"integer\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"holder_first_name\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": true,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"holder_last_name\\\"\",\n                                    \"columnDataType\": \"text\",\n                                    \"pii\": true,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"income\\\"\",\n                                    \"columnDataType\": \"integer\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"is_business\\\"\",\n                                    \"columnDataType\": \"boolean\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                }\n                            ]\n                        },\n                        {\n                            \"tableName\": \"\\\"transactions\\\"\",\n                            \"columns\": [\n                                {\n                                    \"columnName\": \"\\\"transaction_id\\\"\",\n                                    \"columnDataType\": \"bigint\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"account_id\\\"\",\n                                    \"columnDataType\": \"bigint\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"amount\\\"\",\n                                    \"columnDataType\": \"numeric\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                },\n                                {\n                                    \"columnName\": \"\\\"transaction_datetime\\\"\",\n                                    \"columnDataType\": \"timestamp without time zone\",\n                                    \"pii\": false,\n                                    \"piiScanIgnore\": false\n                                }\n                            ]\n                        }\n                    ]\n                }\n            ]\n        }\n    ],\n    \"pagination\": null\n}"}],"_postman_id":"b029bdcb-4b10-4815-b302-cad326f30a22"},{"name":"Start PII Scan","id":"16346eac-11d3-43cd-a007-f10caf2092eb","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/scan","description":"<p>Starts a scan that finds columns with personal data (PII), such as names, emails and phone numbers. It runs in the background, schema by schema.</p>\n<p>💡 This uses the workspace <strong>System LLM</strong> (see <strong>LLMs</strong>).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The scan list, like <strong>Get PII Scans</strong>. The new scan is first, with <code>scanStatus</code> <code>scanning</code> per schema.</p>\n<p>⚠️ <strong>Before you start:</strong> the datasource needs a <strong>standard</strong> snapshot (see <strong>Refresh Snapshot</strong>), because PVML looks at data samples. Only one scan can run at a time.</p>\n<p>💡 The scan only suggests. Review the result and save the columns you agree with in the PII catalog (<strong>Add to Catalog</strong>).</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","pii","scan"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"406572b1-4be2-4962-9fd4-65cb737519d5","name":"Start PII Scan - 200 OK","originalRequest":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/scan"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"catalogs\": [\n        {\n            \"startTime\": \"2026-10-30T01:07:06.04Z\",\n            \"data\": {\n                \"userId\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n                \"userEmail\": \"jane.doe@example.com\",\n                \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n                \"id\": \"49a20558-9cd3-5176-a542-2641e5ee7b60\",\n                \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n                \"scanStatusArr\": [\n                    {\n                        \"tree\": {\n                            \"schemaName\": \"`my-project`.`core`\",\n                            \"tables\": [\n                                {\n                                    \"tableName\": \"`accounts`\",\n                                    \"columns\": [\n                                        {\n                                            \"columnName\": \"`account_id`\",\n                                            \"columnDataType\": \"INT64\",\n                                            \"pii\": false\n                                        },\n                                        {\n                                            \"columnName\": \"`address_city`\",\n                                            \"columnDataType\": \"STRING\",\n                                            \"pii\": true\n                                        },\n                                        {\n                                            \"columnName\": \"`address_country_iso3`\",\n                                            \"columnDataType\": \"STRING\",\n                                            \"pii\": false\n                                        },\n                                        {\n                                            \"columnName\": \"`branch_id`\",\n                                            \"columnDataType\": \"INT64\",\n                                            \"pii\": false\n                                        }\n                                    ]\n                                },\n                                {\n                                    \"tableName\": \"`branches`\",\n                                    \"columns\": [\n                                        {\n                                            \"columnName\": \"`branch_city`\",\n                                            \"columnDataType\": \"STRING\",\n                                            \"pii\": false\n                                        },\n                                        {\n                                            \"columnName\": \"`branch_country_iso3`\",\n                                            \"columnDataType\": \"STRING\",\n                                            \"pii\": false\n                                        },\n                                        {\n                                            \"columnName\": \"`branch_id`\",\n                                            \"columnDataType\": \"INT64\",\n                                            \"pii\": false\n                                        },\n                                        {\n                                            \"columnName\": \"`branch_name`\",\n                                            \"columnDataType\": \"STRING\",\n                                            \"pii\": false\n                                        }\n                                    ]\n                                }\n                            ]\n                        },\n                        \"scanStatus\": \"completed\",\n                        \"completionTime\": \"2026-10-30T01:07:08.404Z\"\n                    }\n                ]\n            }\n        }\n    ],\n    \"pagination\": {\n        \"nextCursor\": null\n    }\n}"}],"_postman_id":"16346eac-11d3-43cd-a007-f10caf2092eb"},{"name":"Get PII Scans","id":"9cd17e01-a365-4eff-9e0a-0b0297955a18","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/scan","description":"<p>Lists the PII scans of a datasource, newest first.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>catalogs</code></strong> (array). Each scan has <code>startTime</code> and <code>data</code>: <code>id</code>, <code>userEmail</code> (who started it) and <code>scanStatusArr</code>. Each schema in <code>scanStatusArr</code> has its <code>tree</code> (tables, columns, and <code>pii</code>: true/false), <code>scanStatus</code> (<code>scanning</code>, <code>completed</code>, <code>failed</code>) and <code>completionTime</code>.</li>\n<li><strong><code>pagination</code></strong> (object): <code>nextCursor</code>.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","pii","scan"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"0704523f-6425-4e6c-8621-a14eb1879e74","name":"Get PII Scans - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/scan"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"catalogs\": [\n        {\n            \"startTime\": \"2026-10-30T01:07:06.04Z\",\n            \"data\": {\n                \"userId\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n                \"userEmail\": \"jane.doe@example.com\",\n                \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n                \"id\": \"49a20558-9cd3-5176-a542-2641e5ee7b60\",\n                \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n                \"scanStatusArr\": [\n                    {\n                        \"tree\": {\n                            \"schemaName\": \"`my-project`.`core`\",\n                            \"tables\": [\n                                {\n                                    \"tableName\": \"`accounts`\",\n                                    \"columns\": [\n                                        {\n                                            \"columnName\": \"`account_id`\",\n                                            \"columnDataType\": \"INT64\",\n                                            \"pii\": false\n                                        },\n                                        {\n                                            \"columnName\": \"`address_city`\",\n                                            \"columnDataType\": \"STRING\",\n                                            \"pii\": true\n                                        },\n                                        {\n                                            \"columnName\": \"`address_country_iso3`\",\n                                            \"columnDataType\": \"STRING\",\n                                            \"pii\": false\n                                        },\n                                        {\n                                            \"columnName\": \"`branch_id`\",\n                                            \"columnDataType\": \"INT64\",\n                                            \"pii\": false\n                                        }\n                                    ]\n                                },\n                                {\n                                    \"tableName\": \"`branches`\",\n                                    \"columns\": [\n                                        {\n                                            \"columnName\": \"`branch_city`\",\n                                            \"columnDataType\": \"STRING\",\n                                            \"pii\": false\n                                        },\n                                        {\n                                            \"columnName\": \"`branch_country_iso3`\",\n                                            \"columnDataType\": \"STRING\",\n                                            \"pii\": false\n                                        },\n                                        {\n                                            \"columnName\": \"`branch_id`\",\n                                            \"columnDataType\": \"INT64\",\n                                            \"pii\": false\n                                        },\n                                        {\n                                            \"columnName\": \"`branch_name`\",\n                                            \"columnDataType\": \"STRING\",\n                                            \"pii\": false\n                                        }\n                                    ]\n                                }\n                            ]\n                        },\n                        \"scanStatus\": \"completed\",\n                        \"completionTime\": \"2026-10-30T01:07:08.404Z\"\n                    }\n                ]\n            }\n        }\n    ],\n    \"pagination\": {\n        \"nextCursor\": null\n    }\n}"}],"_postman_id":"9cd17e01-a365-4eff-9e0a-0b0297955a18"},{"name":"Get PII Scan","id":"3917df0b-3bac-4d4c-85b2-cfc3ba89af68","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/scan/{{scan_id}}","description":"<p>Returns one PII scan.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n<li><strong><code>scan_id</code></strong> (string): The scan <code>id</code> from <strong>Get PII Scans</strong>.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>scanData</code></strong>: one scan, in the format of <strong>Get PII Scans</strong>.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","pii","scan","{{scan_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"1702f308-4e1d-41be-8bf3-aaf0ecd25c9e","name":"Get PII Scan - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/scan/{{scan_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"scanData\": {\n        \"startTime\": \"2026-10-30T01:07:06.04Z\",\n        \"data\": {\n            \"userId\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n            \"userEmail\": \"jane.doe@example.com\",\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"id\": \"49a20558-9cd3-5176-a542-2641e5ee7b60\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"scanStatusArr\": [\n                {\n                    \"tree\": {\n                        \"schemaName\": \"`my-project`.`core`\",\n                        \"tables\": [\n                            {\n                                \"tableName\": \"`accounts`\",\n                                \"columns\": [\n                                    {\n                                        \"columnName\": \"`account_id`\",\n                                        \"columnDataType\": \"INT64\",\n                                        \"pii\": false\n                                    },\n                                    {\n                                        \"columnName\": \"`address_city`\",\n                                        \"columnDataType\": \"STRING\",\n                                        \"pii\": true\n                                    },\n                                    {\n                                        \"columnName\": \"`address_country_iso3`\",\n                                        \"columnDataType\": \"STRING\",\n                                        \"pii\": false\n                                    },\n                                    {\n                                        \"columnName\": \"`branch_id`\",\n                                        \"columnDataType\": \"INT64\",\n                                        \"pii\": false\n                                    }\n                                ]\n                            },\n                            {\n                                \"tableName\": \"`branches`\",\n                                \"columns\": [\n                                    {\n                                        \"columnName\": \"`branch_city`\",\n                                        \"columnDataType\": \"STRING\",\n                                        \"pii\": false\n                                    },\n                                    {\n                                        \"columnName\": \"`branch_country_iso3`\",\n                                        \"columnDataType\": \"STRING\",\n                                        \"pii\": false\n                                    },\n                                    {\n                                        \"columnName\": \"`branch_id`\",\n                                        \"columnDataType\": \"INT64\",\n                                        \"pii\": false\n                                    },\n                                    {\n                                        \"columnName\": \"`branch_name`\",\n                                        \"columnDataType\": \"STRING\",\n                                        \"pii\": false\n                                    }\n                                ]\n                            }\n                        ]\n                    },\n                    \"scanStatus\": \"completed\",\n                    \"completionTime\": \"2026-10-30T01:07:08.404Z\"\n                }\n            ]\n        }\n    }\n}"}],"_postman_id":"3917df0b-3bac-4d4c-85b2-cfc3ba89af68"},{"name":"Get Latest PII Snapshot","id":"daf0d73a-90c3-4714-9520-d633ef41878e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/snapshot/latest","description":"<p>Returns the current PII catalog of the datasource: the columns that are marked as personal data.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>catalog</code></strong>: <code>changeTime</code>, and <code>data</code> with <code>userEmail</code> (who saved it last) and <code>tree</code> (schemas, tables, PII columns). <code>tree</code> is <code>null</code> when nothing is marked yet.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","pii","snapshot","latest"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"4622d858-36c1-4f7b-813e-f6b64d93ea6a","name":"Get Latest PII Snapshot - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/pii/snapshot/latest"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"catalog\": {\n        \"changeTime\": \"2026-10-30T10:00:00Z\",\n        \"data\": {\n            \"userId\": \"8d117eb2-ff29-5364-96b8-6f6f65091ce4\",\n            \"userEmail\": \"jane.doe@example.com\",\n            \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n            \"id\": \"9f99709b-0c65-5480-891f-72eca2386b90\",\n            \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n            \"tree\": [\n                {\n                    \"schemaName\": \"`my-project`.`core`\",\n                    \"tables\": [\n                        {\n                            \"tableName\": \"`accounts`\",\n                            \"columns\": [\n                                {\n                                    \"columnName\": \"`holder_first_name`\",\n                                    \"columnDataType\": \"STRING\",\n                                    \"filterType\": \"text\"\n                                },\n                                {\n                                    \"columnName\": \"`holder_last_name`\",\n                                    \"columnDataType\": \"STRING\",\n                                    \"filterType\": \"text\"\n                                }\n                            ]\n                        }\n                    ]\n                }\n            ]\n        }\n    }\n}"}],"_postman_id":"daf0d73a-90c3-4714-9520-d633ef41878e"}],"id":"165f3b8b-2846-44c4-8ee3-cc63c2befb14","description":"<p>💡 <strong>What is PII?</strong></p>\n<p><strong>PII</strong> stands for <strong>Personally Identifiable Information</strong>. It refers to any data that can be used to identify a specific individual, for example:</p>\n<ul>\n<li><p>Full name</p>\n</li>\n<li><p>Phone number</p>\n</li>\n<li><p>Social security number</p>\n</li>\n<li><p>Credit card or bank account number</p>\n</li>\n</ul>\n<p>By tagging fields as PII, organizations can easily identify protected data, enforce stricter access controls, and tailor policies to restrict access based on user roles, responsibilities, and business use cases.</p>\n<p>This approach not only supports compliance with privacy regulations but also minimizes exposure of sensitive data, reduces breach risks, and ensures that only authorized users or systems have access to critical information.</p>\n<p>💡 <strong>How does the PII Catalog Work?</strong></p>\n<ol>\n<li><p>Begin by either tagging the schemas structure manually or running an automatic PII scan (<strong>Start PII Scan</strong>)</p>\n</li>\n<li><p>Resolve conflicts between the scan and the current version by accepting the relevant suggestions from the scan</p>\n</li>\n<li><p>Tagging fields as ignored means you want to exclude the scan's suggestions for this field (either as PII or as non-PII) - in practice, this means that performing \"merge\" will ignore the scan's tag for these fields.</p>\n</li>\n</ol>\n<p>💡 <strong>Merge vs. Replace</strong></p>\n<ul>\n<li><p>Merge takes everything from the scan over the current version, while keeping the extra tags in current version</p>\n</li>\n<li><p>Replace takes the scan as-is over the current version</p>\n</li>\n</ul>\n","_postman_id":"165f3b8b-2846-44c4-8ee3-cc63c2befb14","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}}],"id":"b5c7f038-0ae0-45cb-98b8-6ce04f9dd28a","description":"<img src=\"https://pvml.com/wp-content/uploads/2023/12/img-product-hero.svg\" />\n\n<p>Data sources can be connected to your PVML workspace using connection string properties, without the need to move or duplicate the data. Once the data source is connected, a snapshot of the data schema (the structure of tables and columns) will be pulled.</p>\n<p>You can choose between:</p>\n<ol>\n<li><p><strong>Standard snapshot:</strong> reads the structure and data samples. Needed for <strong>Differential Privacy</strong> and the <strong>PII scan</strong>.</p>\n</li>\n<li><p><strong>Schema-only snapshot:</strong> reads only schemas, tables and columns, no data samples. Faster, but Differential Privacy and the PII scan are not available.</p>\n</li>\n</ol>\n<p>💡 <strong>Names in the API:</strong> in <strong>Refresh Snapshot</strong>, <code>profile=true</code> is Standard and <code>profile=false</code> is Schema-only. <strong>Get Snapshot Status</strong> returns <code>standard</code> or <code>schema_only</code>.</p>\n<img src=\"https://content.pstmn.io/c512474e-33b2-43ab-af40-9fee2a0db366/U2NyZWVuc2hvdCAyMDI2LTAyLTE4IGF0IDE2LjE1LjIyLnBuZw==\" alt=\"Connect%20a%20Datasource\" />\n\n<p>After the schema snapshot is complete, you can define relations, identifiers, and more.</p>\n","_postman_id":"b5c7f038-0ae0-45cb-98b8-6ce04f9dd28a","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Virtual DBs","item":[{"name":"Create Virtual DB","id":"f0ee22e1-ec0f-45c4-a3e6-d8f64c163a06","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"name\": \"clinic analytics\",\n    \"description\": \"VDB for the clinic analytics team\",\n    \"datasourceId\": \"{{datasource_id}}\",\n    \"createRegularVdbInfo\": {\n        \"shouldEnforce\": false,\n        \"permissionsIds\": [\n            \"{{policy_id}}\"\n        ],\n        \"entitiesIds\": [\n            {\n                \"type\": \"user\",\n                \"id\": \"{{user_id}}\"\n            },\n            {\n                \"type\": \"group\",\n                \"id\": \"{{group_id}}\"\n            }\n        ],\n        \"bypassSystemTablesEnforcement\": false\n    }\n}"},{"description":"<p>Optional image file (PNG/JPG).</p>\n","key":"image","type":"file","value":null,"disabled":true}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs","description":"<p>Creates a new VDB (virtual database) on top of a datasource.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>Form data with these fields:</p>\n<ul>\n<li><strong><code>data</code></strong> (text): JSON with these properties:<ul>\n<li><strong><code>name</code></strong> (string, required): The name of the VDB.</li>\n<li><strong><code>description</code></strong> (string): A short description.</li>\n<li><strong><code>datasourceId</code></strong> (string, required): The datasource the VDB is built on.</li>\n<li><strong><code>createRegularVdbInfo</code></strong> (object, required): The VDB settings:<ul>\n<li><strong><code>permissionsIds</code></strong> (array of strings): The policies to attach. Add at least one column policy, otherwise users see no data.</li>\n<li><strong><code>entitiesIds</code></strong> (array): The users and groups that get access. Each item is <code>{\"type\": \"user\" | \"group\", \"id\": \"&lt;id&gt;\"}</code>.</li>\n<li><strong><code>shouldEnforce</code></strong> (boolean): \"Propagate policies\". When <code>true</code>, PVML derives policies for database views in this VDB from their definitions, based on the source data and the policies of this VDB. Default <code>false</code>.</li>\n<li><strong><code>enforcementVdbId</code></strong> (string, optional): Derive the view policies from another VDB instead. Used only when <code>shouldEnforce</code> is <code>true</code>.</li>\n<li><strong><code>bypassSystemTablesEnforcement</code></strong> (boolean): When <code>false</code> (default), users and BI tools can query only the system metadata objects (for example information_schema) that their column policy allows. When <code>true</code>, all scanned system metadata is visible without policy limits.</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><strong><code>image</code></strong> (file, optional): An image for the VDB.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A <strong><code>vdb</code></strong> object:</p>\n<ul>\n<li><strong><code>vdbId</code></strong> (string): The VDB ID.</li>\n<li><strong><code>datasourceId</code></strong>, <strong><code>datasourceType</code></strong>, <strong><code>workspaceId</code></strong> (string).</li>\n<li><strong><code>name</code></strong>, <strong><code>description</code></strong> (string).</li>\n<li><strong><code>shouldEnforce</code></strong> (boolean).</li>\n<li><strong><code>createdAt</code></strong>, <strong><code>lastModifiedAt</code></strong>, <strong><code>permissionsLastModifiedAt</code></strong> (number): Unix time in milliseconds.</li>\n<li><strong><code>image</code></strong> (string).</li>\n<li><strong><code>permissionsIds</code></strong> (array): The attached policy IDs.</li>\n<li><strong><code>entities</code></strong> (array): The users and groups with access, as <code>{type, id}</code>.</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"c42bad17-b0be-4caf-8c67-1b11906347e7","name":"Create Virtual DB - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"name\": \"clinic analytics\",\n    \"description\": \"VDB for the clinic analytics team\",\n    \"datasourceId\": \"{{datasource_id}}\",\n    \"createRegularVdbInfo\": {\n        \"shouldEnforce\": false,\n        \"permissionsIds\": [\n            \"{{policy_id}}\"\n        ],\n        \"entitiesIds\": [\n            {\n                \"type\": \"user\",\n                \"id\": \"{{user_id}}\"\n            },\n            {\n                \"type\": \"group\",\n                \"id\": \"{{group_id}}\"\n            }\n        ],\n        \"bypassSystemTablesEnforcement\": false\n    }\n}"},{"description":"Optional image file (PNG/JPG).","key":"image","type":"file","src":[],"disabled":true}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"vdb\": {\n        \"vdbId\": \"df8fe2c3-7c75-5f3e-8d47-d08181298485\",\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceType\": \"BigQuery\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"name\": \"clinic analytics\",\n        \"description\": \"\",\n        \"shouldEnforce\": false,\n        \"permissionsLastModifiedAt\": 1793318158530,\n        \"createdAt\": 1793318158530,\n        \"lastModifiedAt\": 1793318158530,\n        \"image\": \"\",\n        \"permissionsIds\": [\n            \"f2214a61-7e05-5526-85cd-df7b98635e6d\"\n        ],\n        \"entities\": [\n            {\n                \"type\": \"user\",\n                \"id\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\"\n            }\n        ]\n    }\n}"}],"_postman_id":"f0ee22e1-ec0f-45c4-a3e6-d8f64c163a06"},{"name":"Get Virtual DB","id":"c246e737-84ae-4f6b-acd8-54857cfc5dd1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}?image=false","description":"<p>This endpoint allows to get a specific VDB in a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>vdb_id</code></strong> (string): The unique identifier of the new VDB.</p>\n</li>\n<li><p><strong><code>image</code></strong> (boolean): Specifies whether to include image data in the response.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p><strong><code>vdb</code></strong> json containing:</p>\n<ul>\n<li><p><strong><code>vdbId</code></strong> (string): Unique identifier for the VDB.</p>\n</li>\n<li><p><strong><code>datasourceId</code></strong> (string): Identifier for the associated data source.</p>\n</li>\n<li><p><strong><code>datasourceType</code></strong> (string): Type of the datasource (e.g., Postgres).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): Identifier for the workspace where the VDB is located.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): Descriptive name of the VDB.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): Additional information or context about the VDB.</p>\n</li>\n<li><p><strong><code>createdAt</code></strong> (timestamp, Unix epoch in milliseconds): Timestamp of VDB creation.</p>\n</li>\n<li><p><strong><code>lastModifiedAt</code></strong> (timestamp, Unix epoch in milliseconds): Timestamp of the most recent modification to the VDB.</p>\n</li>\n<li><p><strong><code>permissionsLastModifiedAt</code></strong> (timestamp, Unix epoch in milliseconds): Timestamp of the last update to policies in the VDB.</p>\n</li>\n<li><p><strong><code>image</code></strong> (string): Encoded image representation or thumbnail of the VDB.</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}"],"host":["platform","pvml","com"],"query":[{"key":"image","value":"false"}],"variable":[]}},"response":[{"id":"dad1cb5e-5c2b-427f-895e-66e7333f6dd0","name":"Get Virtual DB - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}?image=false","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}"],"query":[{"key":"image","value":"false"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"vdb\": {\n        \"vdbId\": \"df8fe2c3-7c75-5f3e-8d47-d08181298485\",\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceType\": \"BigQuery\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"name\": \"clinic analytics\",\n        \"description\": \"VDB for the clinic analytics team\",\n        \"shouldEnforce\": false,\n        \"permissionsLastModifiedAt\": 1793318159168,\n        \"createdAt\": 1793318158530,\n        \"lastModifiedAt\": 1793318159171\n    }\n}"}],"_postman_id":"c246e737-84ae-4f6b-acd8-54857cfc5dd1"},{"name":"Get Virtual DB Users","id":"8a19a649-1700-4f98-8a69-17e4666360d8","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/entities","description":"<p>This endpoint allows to get the users of a specific VDB in a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<ul>\n<li><p><strong><code>entities</code></strong> (array):</p>\n<ul>\n<li><p><strong><code>type</code></strong> (string): The type of entity (group / user. Note: a service account is considered a user in this context).</p>\n</li>\n<li><p><strong><code>group</code></strong> (object or null): Contains group-related details if the entity is of type <code>group</code>.</p>\n<ul>\n<li><p><strong><code>groupId</code></strong> (string): Unique identifier for the group.</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): Identifier for the workspace to which the group belongs.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): Name of the group.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): Description or additional details about the group.</p>\n</li>\n<li><p><strong><code>createdAt</code></strong> (timestamp, ISO 8601 format): Timestamp of group creation.</p>\n</li>\n<li><p><strong><code>lastModifiedAt</code></strong> (timestamp, ISO 8601 format): Timestamp of the last modification to the group.</p>\n</li>\n<li><p><strong><code>image</code></strong> (string): Encoded image representation of the group.</p>\n</li>\n</ul>\n</li>\n<li><p><strong><code>user</code></strong> (object or null): Contains user-related details if the entity is of type <code>user</code>.</p>\n</li>\n</ul>\n</li>\n</ul>\n<p>💡 The response also has a <strong><code>pagination</code></strong> object: <code>totalRecords</code>, <code>currentPage</code>, <code>totalPages</code>, <code>pageSize</code>, <code>nextPage</code>, <code>prevPage</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","entities"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"29438cdf-48e0-48b2-ae4b-40b414a2ab76","name":"Get Virtual DB Users - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/entities"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"entities\": [\n        {\n            \"type\": \"group\",\n            \"group\": {\n                \"id\": \"d8c07ec8-f3e6-5482-99c3-e708d18bebab\",\n                \"name\": \"analysts\",\n                \"image\": \"\"\n            },\n            \"user\": null\n        },\n        {\n            \"type\": \"user\",\n            \"group\": null,\n            \"user\": {\n                \"id\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n                \"email\": \"jane.doe@example.com\",\n                \"created_at\": \"0001-01-01T00:00:00Z\",\n                \"image\": \"\"\n            }\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 2,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 2,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"8a19a649-1700-4f98-8a69-17e4666360d8"},{"name":"Get Full Virtual DB","id":"d18c1fe6-c57b-414e-89d4-b018bbac633f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/full?image=false","description":"<p>This endpoint allows to get a full VDB in a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>vdb_id</code></strong> (string): The unique identifier of the new VDB.</p>\n</li>\n<li><p><strong><code>/full</code></strong>: returns also <code>permissionsIds</code> and <code>entities</code> in the response.</p>\n</li>\n<li><p><strong><code>image</code></strong> (boolean): Specifies whether to include image data in the response.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<ul>\n<li><p><strong><code>vdbId</code></strong> (string): Unique identifier for the VDB.</p>\n</li>\n<li><p><strong><code>datasourceId</code></strong> (string): Identifier for the associated data source.</p>\n</li>\n<li><p><strong><code>datasourceType</code></strong> (string): Type of the datasource (e.g., Postgres).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): Identifier for the workspace where the VDB is located.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): Descriptive name of the VDB.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): Additional information or context about the VDB.</p>\n</li>\n<li><p><strong><code>createdAt</code></strong> (timestamp, Unix epoch in milliseconds): Timestamp of VDB creation.</p>\n</li>\n<li><p><strong><code>lastModifiedAt</code></strong> (timestamp, Unix epoch in milliseconds): Timestamp of the most recent modification to the VDB.</p>\n</li>\n<li><p><strong><code>permissionsLastModifiedAt</code></strong> (timestamp, Unix epoch in milliseconds): Timestamp of the last update to policies in the VDB.</p>\n</li>\n<li><p><strong><code>image</code></strong> (string): Encoded image representation or thumbnail of the VDB (returned if <code>image</code> parameter is set to <code>true</code>).</p>\n</li>\n<li><p><strong><code>permissionsIds</code></strong> (array or null): List of policies' IDs associated with the VDB, if any.</p>\n</li>\n<li><p><strong><code>entities</code></strong> (array): List of related users or groups associated with the VDB.</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","full"],"host":["platform","pvml","com"],"query":[{"key":"image","value":"false"}],"variable":[]}},"response":[{"id":"6dbbe27b-1e7b-4883-b0b2-e020cc04c5bb","name":"Get Full Virtual DB - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/full?image=false","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","full"],"query":[{"key":"image","value":"false"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"vdb\": {\n        \"vdbId\": \"df8fe2c3-7c75-5f3e-8d47-d08181298485\",\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceType\": \"BigQuery\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"name\": \"clinic analytics\",\n        \"description\": \"VDB for the clinic analytics team\",\n        \"shouldEnforce\": false,\n        \"permissionsLastModifiedAt\": 1793318159168,\n        \"createdAt\": 1793318158530,\n        \"lastModifiedAt\": 1793318159171,\n        \"permissionsIds\": [\n            \"ac2ffc71-740d-5e6f-b54e-75ad0e3dce3e\",\n            \"f2214a61-7e05-5526-85cd-df7b98635e6d\"\n        ],\n        \"entities\": [\n            {\n                \"type\": \"user\",\n                \"id\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\"\n            },\n            {\n                \"type\": \"group\",\n                \"id\": \"d8c07ec8-f3e6-5482-99c3-e708d18bebab\"\n            }\n        ]\n    }\n}"}],"_postman_id":"d18c1fe6-c57b-414e-89d4-b018bbac633f"},{"name":"Get Virtual DBs","id":"12a8a51e-31c2-47f3-b50b-1d48a095450c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs","description":"<p>This endpoint allows to get all the VDBs in a specific workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><code>workspace_id</code>(string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p>The response is a <strong><code>vdbs</code></strong> object in json format that includes an array of VDBs with the following details on each VDB:</p>\n<ul>\n<li><p><strong><code>vdbId</code></strong> (string): Unique identifier for the VDB.</p>\n</li>\n<li><p><strong><code>datasourceId</code></strong> (string): Identifier for the associated data source.</p>\n</li>\n<li><p><strong><code>datasourceType</code></strong> (string): Type of the datasource (e.g., Postgres).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): Identifier for the workspace where the VDB is located.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): Descriptive name of the VDB.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): Additional information or context about the VDB.</p>\n</li>\n<li><p><strong><code>createdAt</code></strong> (timestamp, Unix epoch in milliseconds): Timestamp of VDB creation.</p>\n</li>\n<li><p><strong><code>lastModifiedAt</code></strong> (timestamp, Unix epoch in milliseconds): Timestamp of the most recent modification to the VDB.</p>\n</li>\n<li><p><strong><code>permissionsLastModifiedAt</code></strong> (timestamp, Unix epoch in milliseconds): Timestamp of the last update to policies in the VDB.</p>\n</li>\n<li><p><strong><code>image</code></strong> (string): Encoded image representation or thumbnail of the VDB.</p>\n</li>\n</ul>\n<p>💡 The response also has a <strong><code>pagination</code></strong> object: <code>totalRecords</code>, <code>currentPage</code>, <code>totalPages</code>, <code>pageSize</code>, <code>nextPage</code>, <code>prevPage</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"eba9881c-4e31-4708-9adb-b62ebe2acc20","name":"Get Virtual DBs - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"vdbs\": [\n        {\n            \"datasourceName\": \"MyDB\",\n            \"vdbId\": \"df8fe2c3-7c75-5f3e-8d47-d08181298485\",\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceType\": \"BigQuery\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"name\": \"clinic analytics\",\n            \"description\": \"VDB for the clinic analytics team\",\n            \"shouldEnforce\": false,\n            \"permissionsLastModifiedAt\": 1793318159168,\n            \"createdAt\": 1793318158530,\n            \"lastModifiedAt\": 1793318159171\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 1,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 1,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"12a8a51e-31c2-47f3-b50b-1d48a095450c"},{"name":"Get Virtual DB Schemas","id":"b7da8b4e-7dee-4e0c-8bd9-0581ba5ca6ed","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/tree/display","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","tree","display"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"331f15ed-1b77-4ca6-a6ff-88120082a7e2","name":"Get Virtual DB Schemas - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/tree/display"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"tree\": {\n        \"datasourceName\": \"MyDB\",\n        \"datasourceType\": \"BigQuery\",\n        \"schemas\": [\n            {\n                \"schemaName\": \"`my-project`.`clinic`\",\n                \"tables\": [\n                    {\n                        \"tableName\": \"`doctors`\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"`id`\",\n                                \"columnDataType\": \"STRING\"\n                            },\n                            {\n                                \"columnName\": \"`name`\",\n                                \"columnDataType\": \"STRING\"\n                            }\n                        ]\n                    },\n                    {\n                        \"tableName\": \"`patients`\",\n                        \"columns\": [\n                            {\n                                \"columnName\": \"`age`\",\n                                \"columnDataType\": \"NUMERIC\"\n                            },\n                            {\n                                \"columnName\": \"`doctor`\",\n                                \"columnDataType\": \"STRING\"\n                            },\n                            {\n                                \"columnName\": \"`gender`\",\n                                \"columnDataType\": \"STRING\"\n                            },\n                            {\n                                \"columnName\": \"`height`\",\n                                \"columnDataType\": \"BIGNUMERIC\"\n                            },\n                            {\n                                \"columnName\": \"`hospital`\",\n                                \"columnDataType\": \"STRING\"\n                            },\n                            {\n                                \"columnName\": \"`id`\",\n                                \"columnDataType\": \"BIGNUMERIC\"\n                            },\n                            {\n                                \"columnName\": \"`is_smoker`\",\n                                \"columnDataType\": \"BOOL\"\n                            },\n                            {\n                                \"columnName\": \"`name`\",\n                                \"columnDataType\": \"STRING\"\n                            },\n                            {\n                                \"columnName\": \"`weighty`\",\n                                \"columnDataType\": \"FLOAT64\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ]\n    }\n}"}],"_postman_id":"b7da8b4e-7dee-4e0c-8bd9-0581ba5ca6ed"},{"name":"Update Virtual DB Info","id":"bc009684-a689-47a9-baba-fb7c5e126279","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PUT","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"name\": \"clinic analytics\",\n    \"description\": \"VDB for the clinic analytics team\",\n    \"shouldEnforce\": false,\n    \"bypassSystemTablesEnforcement\": false\n}"},{"description":"<p>Optional image file (PNG/JPG).</p>\n","key":"image","type":"file","value":null,"disabled":true}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/info","description":"<p>Updates the name, description, settings and image of a VDB.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>Form data with these fields:</p>\n<ul>\n<li><strong><code>data</code></strong> (text): JSON with these properties:<ul>\n<li><strong><code>name</code></strong> (string, required): The VDB name.</li>\n<li><strong><code>description</code></strong> (string): The VDB description.</li>\n<li><strong><code>shouldEnforce</code></strong> (boolean, required): See <strong>Create Virtual DB</strong>.</li>\n<li><strong><code>enforcementVdbId</code></strong> (string, optional).</li>\n<li><strong><code>bypassSystemTablesEnforcement</code></strong> (boolean, required): See <strong>Create Virtual DB</strong>.</li>\n</ul>\n</li>\n<li><strong><code>image</code></strong> (file, optional): The VDB image.</li>\n</ul>\n<p>💡 <strong>Note:</strong> Fields you leave out are reset. For example, if you do not send <code>image</code>, the image goes back to the default.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","info"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"bdfabe77-9abc-4271-a5f6-7be249021850","name":"Update Virtual DB Info - 200 OK","originalRequest":{"method":"PUT","header":[],"body":{"mode":"formdata","formdata":[{"key":"data","type":"text","value":"{\n    \"name\": \"clinic analytics\",\n    \"description\": \"VDB for the clinic analytics team\",\n    \"shouldEnforce\": false,\n    \"bypassSystemTablesEnforcement\": false\n}"},{"description":"Optional image file (PNG/JPG).","key":"image","type":"file","src":[],"disabled":true}]},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/info"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"bc009684-a689-47a9-baba-fb7c5e126279"},{"name":"Update Virtual DB Users","id":"ce543bb7-e5d3-45fd-b288-ecd9e2d8a00e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"entitiesToAdd\": [\n        {\n            \"type\": \"group\",\n            \"id\": \"{{group_id}}\"\n        }\n    ],\n    \"entitiesToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/entities","description":"<p>Adds or removes users and groups that can access a VDB.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>entitiesToAdd</code></strong> (array): Users and groups to add. Each item has:<ul>\n<li><strong><code>type</code></strong> (string): <code>user</code> or <code>group</code>.</li>\n<li><strong><code>id</code></strong> (string): The user ID or group ID.</li>\n</ul>\n</li>\n<li><strong><code>entitiesToRemove</code></strong> (array): Users and groups to remove, in the same format.</li>\n</ul>\n<p>Both arrays are required. Send <code>[]</code> when there is nothing to add or remove.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body. If nothing changes (for example the user is already in the VDB), the status is 304 Not Modified.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","entities"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"ed481998-a584-468b-832d-7a1a795edc68","name":"Update Virtual DB Users - 200 OK","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"entitiesToAdd\": [\n        {\n            \"type\": \"group\",\n            \"id\": \"{{group_id}}\"\n        }\n    ],\n    \"entitiesToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/entities"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"ce543bb7-e5d3-45fd-b288-ecd9e2d8a00e"},{"name":"Update Virtual DB Policies","id":"c6face02-a48d-4bf7-9523-1992761e2b12","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"permissionsToAdd\": [\"{{row_policy}}\"],\n    \"permissionsToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/permissions","description":"<p>This endpoint allows to update the policies of a specific VDB in a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<ul>\n<li><p><strong><code>permissionsToAdd</code></strong> (array)<strong>:</strong> A list of policies to be added.</p>\n</li>\n<li><p><strong><code>permissionsToRemove</code></strong> (array)<strong>:</strong> A list of policies to be removed. Same format as <code>permissionsToAdd</code>.</p>\n</li>\n</ul>\n<p><strong>Note:</strong> Each array can be left empty (<code>[]</code>) if no policies need to be added or removed.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p>Upon successful update, the server returns a status code of 200 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"f4af254c-e092-4c4b-b6ab-78e8132e6039","name":"Update View Policies - 200 Success","originalRequest":{"method":"PATCH","header":[],"body":{"mode":"raw","raw":"{\n    \"permissionsToAdd\": [\"{{row_policy}}\"],\n    \"permissionsToRemove\": []\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/permissions"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"\"\""}],"_postman_id":"c6face02-a48d-4bf7-9523-1992761e2b12"},{"name":"Delete Virtual DB","id":"ccad905f-30b4-4ba0-ba79-c125f19bed46","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}","description":"<p>This endpoint allows to delete a specific VDB in a workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p>Upon successful deletion, the server returns a status code of 200 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"da046d59-4564-4cda-b261-e914986c9b62","name":"Delete View - 200 Success","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"\"\""}],"_postman_id":"ccad905f-30b4-4ba0-ba79-c125f19bed46"},{"name":"Execute Query on Virtual DB","id":"11605a35-2e40-4315-9e96-8382712de48e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"query\": \"select city, count(*) as accounts from \\\"bank\\\".\\\"core\\\".\\\"accounts\\\" a join \\\"bank\\\".\\\"core\\\".\\\"branches\\\" b on a.branch_id = b.branch_id group by city\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/execute","description":"<p>This endpoint allows to directly execute a query using a VDB.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>query</code></strong> (string): the SQL query to execute.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The response represents a <strong>database query result</strong> in a tabular format:</p>\n<ul>\n<li><p><strong>Outer Array</strong>: Contains rows of the result set.</p>\n</li>\n<li><p><strong>First Inner Array</strong>: Represents the <strong>column</strong> <strong>headers</strong> (first row of the table).</p>\n</li>\n<li><p><strong>Subsequent Inner Arrays</strong>: Represent individual <strong>rows</strong> <strong>of data</strong> from the query result.</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","execute"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"fb62628d-aed5-4ddd-8e77-5b90acad249f","name":"Execute Query - 200 Success","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"query\": \"select city, count(*) as accounts from \\\"bank\\\".\\\"core\\\".\\\"accounts\\\" a join \\\"bank\\\".\\\"core\\\".\\\"branches\\\" b on a.branch_id = b.branch_id group by city\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/execute"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"[\n    [\n        \"?column?\"\n    ],\n    [\n        1\n    ]\n]"}],"_postman_id":"11605a35-2e40-4315-9e96-8382712de48e"},{"name":"Duplicate Virtual DB","id":"37a402a2-694a-4904-addc-2e463afab732","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"duplicateVdbPermission\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/duplicate","description":"<p>Creates a copy of a VDB, named <code>&lt;name&gt; - copy</code>, with the same users, groups and settings.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>duplicateVdbPermission</code></strong> (boolean):<ul>\n<li><code>false</code>: the copy uses the <strong>same</strong> policies. A change to a policy changes both VDBs.</li>\n<li><code>true</code>: PVML also copies each policy (<code>&lt;name&gt; - copy</code>). You can change the copy without touching the original VDB.</li>\n</ul>\n</li>\n</ul>\n<p>💡 Use <code>true</code> when you want to try changes safely, for example on a test VDB.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 201 with <strong><code>duplicateVdb</code></strong>: the new VDB, including <code>regularViewInfo</code> with <code>permissionsIds</code>, <code>groupsIds</code> and <code>directUsersIds</code>.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","duplicate"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"738dbc14-6555-4a3f-a01e-9b7e98dbc515","name":"Duplicate Virtual DB - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"duplicateVdbPermission\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/duplicate"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"duplicateVdb\": {\n        \"vdbId\": \"35df65db-5123-5285-ae70-a99aa04bc60e\",\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceType\": \"BigQuery\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"name\": \"branch analytics - copy\",\n        \"description\": \"\",\n        \"shouldEnforce\": false,\n        \"permissionsLastModifiedAt\": 1793322349246,\n        \"createdAt\": 1793322349227,\n        \"lastModifiedAt\": 1793322349227,\n        \"image\": \"\",\n        \"regularViewInfo\": {\n            \"permissionsIds\": [\n                \"bcc215c4-2e6c-57c0-be1d-8448cc1765a3\"\n            ],\n            \"groupsIds\": [],\n            \"directUsersIds\": [\n                \"4599af59-6602-58a4-97e0-a0b5016bcc58\"\n            ]\n        }\n    }\n}"}],"_postman_id":"37a402a2-694a-4904-addc-2e463afab732"},{"name":"Get Virtual DB Policies","id":"703a79b2-36ec-4cdc-a569-07cbe03cc75d","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/permissions","description":"<p>Lists the policies attached to a VDB, with their data.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>permissions</code></strong> (array): Policy objects.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"12ccefa3-e4dc-4f59-9fb1-58e808aa2154","name":"Get Virtual DB Policies - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/permissions"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permissions\": [\n        {\n            \"vdbId\": \"607187ab-3f63-5c73-9ce8-779969cce2d1\",\n            \"vdbName\": \"branch analytics\",\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceName\": \"MyDB\",\n            \"datasourceType\": \"BigQuery\",\n            \"id\": \"a8411a99-6e3c-55f5-b22b-e46eda32aeb1\",\n            \"name\": \"core read access\",\n            \"description\": \"read access to the core schema\",\n            \"type\": \"data_access\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"data\": \"[{\\\"table\\\": \\\"*\\\", \\\"column\\\": \\\"*\\\", \\\"schema\\\": \\\"`my-project`.`core`\\\"}]\",\n            \"createdAt\": \"2026-10-30T01:05:48.066941Z\",\n            \"lastModifiedAt\": \"2026-10-30T01:05:48.635997Z\"\n        }\n    ]\n}"}],"_postman_id":"703a79b2-36ec-4cdc-a569-07cbe03cc75d"},{"name":"Get Virtual DB Relations","id":"bfaac09f-681a-42d5-8d57-523c927f817b","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/relations","description":"<p>Returns the relations between tables that the user can see in this VDB.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>schemaRelations</code></strong> (array): Per schema, the <code>relations</code> (same format as <strong>Get Relations</strong>).</li>\n</ul>\n<p>💡 PVML shows only relations between tables and columns the user can access in this VDB. A relation must not reveal a table the user can not see.</p>\n<p>Any workspace member with access to the VDB can call it.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","relations"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"42d55236-c339-4855-a226-9af24a28a502","name":"Get Virtual DB Relations - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/relations"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"schemaRelations\": [\n        {\n            \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n            \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n            \"schema\": \"`my-project`.`core`\",\n            \"relations\": [\n                {\n                    \"referencingTable\": \"`accounts`\",\n                    \"referencedTable\": \"`branches`\",\n                    \"columnsReferences\": [\n                        {\n                            \"referencingColumn\": \"`branch_id`\",\n                            \"referencedColumn\": \"`branch_id`\"\n                        }\n                    ]\n                }\n            ]\n        }\n    ]\n}"}],"_postman_id":"bfaac09f-681a-42d5-8d57-523c927f817b"},{"name":"Get Virtual DB MCP","id":"350bf81d-99b1-401d-ba67-77b7ebf8e7b7","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/mcp","description":"<p>Returns the ID of the MCP server of a VDB. PVML creates one MCP server for every VDB.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>mcpId</code></strong> (string): Use it in the <strong>MCP</strong> requests, for example <strong>Get MCP Connection Config</strong>.</li>\n</ul>\n<p>Any workspace member with access to the VDB can call it.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","mcp"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"49f32e86-9c7e-4b9e-b1d2-034e4d2a68de","name":"Get Virtual DB MCP - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/mcp"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"mcpId\": \"820201bf-8c74-5946-9776-f7653d44c124\"\n}"}],"_postman_id":"350bf81d-99b1-401d-ba67-77b7ebf8e7b7"}],"id":"fd7943c3-453e-44b4-b10a-c74405eb8d12","description":"<p>A virtual database contains a collection of data access policies that determine the scope of data that users can see and interact with, as well as the privacy controls applied to them.</p>\n<p>A virtual database has the following properties:</p>\n<ul>\n<li><p>Datasource</p>\n</li>\n<li><p>Users</p>\n</li>\n<li><p>Policies</p>\n</li>\n</ul>\n<p>Once a virtual database is set up, you can generate <strong>a secure connection string</strong> for it (JDBC/ODBC). See <strong>Connections &gt; ODBC / JDBC</strong>.</p>\n<p>This ensures that all data interactions are processed through PVML, with permissions enforced in real time.</p>\n<img src=\"https://content.pstmn.io/3ca4b39e-8431-4212-8c08-06f26835d319/QVBJIGltYWdlcy5naWY=\" alt=\"Generate%20a%20Secure%20Connection%20String%20for%20a%20virtual%20database%20(JDBC/ODBC).\" />\n\n<p>You can also use this virtual database in the <strong>PVML Notebook</strong>, connect AI clients to it through its <strong>MCP server</strong>, or run SQL queries <strong>directly</strong> through it like any other database.</p>\n<h4 id=\"🕵🏻♂️-common-mistakes-when-configuring-a-virtual-database\">🕵🏻‍♂️ <strong>Common Mistakes When Configuring a V</strong>irtual Database**:**</h4>\n<ol>\n<li><p><strong>Forgetting to Attach a Column-Level Policy</strong>:<br /> A virtual database must have a column-level policy to be considered valid. Ensure this is set up.</p>\n</li>\n<li><p><strong>Not Adding Users</strong>:<br /> Even if you're the admin or owner of the workspace, or even if there's only one user or group in your workspace, you must explicitly add the users/groups to the virtual database.</p>\n</li>\n</ol>\n","_postman_id":"fd7943c3-453e-44b4-b10a-c74405eb8d12","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Policies","item":[{"name":"Guide: Row-level security (RLS)","item":[],"id":"245d231b-5506-4e0c-998b-597d3687f14d","description":"<p>Row-level security (RLS) means: <strong>every user sees only the rows they are allowed to see</strong>, in the same table, with the same query.</p>\n<p>In this guide we build RLS step by step for a simple example.</p>\n<h3 id=\"the-example\">The example</h3>\n<p>A bank has branches in many cities. Its database has three tables in the schema <code>\"bank\".\"core\"</code>:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Table</th>\n<th>Columns (short list)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>branches</code></td>\n<td><code>branch_id</code>, <code>city</code>, <code>country</code></td>\n</tr>\n<tr>\n<td><code>accounts</code></td>\n<td><code>account_id</code>, <code>branch_id</code>, <code>holder_name</code>, <code>income</code></td>\n</tr>\n<tr>\n<td><code>transactions</code></td>\n<td><code>transaction_id</code>, <code>account_id</code>, <code>amount</code></td>\n</tr>\n</tbody>\n</table>\n</div><p>The goal: <strong>each branch manager sees only the accounts and transactions of their own city.</strong> The head office sees everything.</p>\n<hr />\n<h3 id=\"step-1-a-simple-fixed-filter\">Step 1: A simple fixed filter</h3>\n<p>First, a filter that is the same for everyone: show only accounts in the UK.</p>\n<p>Call <strong>Create Row Policy</strong> with this <code>data</code>:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"predicate\": \"\\\"bank\\\".\\\"core\\\".\\\"accounts\\\".\\\"country\\\" = 'GBR'\",\n  \"scope\": [\"\\\"bank\\\".\\\"core\\\".\\\"accounts\\\"\"]\n}\n\n</code></pre>\n<ul>\n<li><p><strong><code>predicate</code></strong> is a normal SQL condition, like the <code>WHERE</code> part of a query. Only rows where it is true are visible.</p>\n</li>\n<li><p><strong><code>scope</code></strong> is the list of tables the filter applies to.</p>\n</li>\n<li><p>Write each column with its <strong>full path</strong> (<code>database.schema.table.column</code>). See <strong>Guide: Writing object names</strong>.</p>\n</li>\n<li><p>The <code>\\\"</code> is only JSON escaping for the API. In the platform, type the predicate as plain SQL: <code>\"bank\".\"core\".\"accounts\".\"country\" = 'GBR'</code>.</p>\n</li>\n</ul>\n<p>Attach the policy to a VDB (together with a column policy). Now <code>SELECT COUNT(\\\\\\*) FROM accounts</code> in this VDB returns only the UK accounts, for example 423 instead of 5,000. The user does not need to add any <code>WHERE</code> clause.</p>\n<hr />\n<h3 id=\"step-2-a-personal-filter-with-user-attributes\">Step 2: A personal filter with user attributes</h3>\n<p>A fixed filter is the same for every user. But Dana manages London and Marc manages Paris. We do not want one policy per manager. Instead, we use <strong>one policy with a user attribute</strong>.</p>\n<p><strong>2a. Create the attribute</strong> (see the <strong>Attributes</strong> section):</p>\n<ol>\n<li><p><strong>Create Attribute Catalog</strong> with name <code>bank</code>.</p>\n</li>\n<li><p><strong>Define Attributes</strong> in it: <code>{\"name\": \"city\", \"type\": \"text\"}</code>.</p>\n</li>\n<li><p><strong>Set User Attributes</strong>: Dana gets <code>city = \"London\"</code>, Marc gets <code>city = \"Paris\"</code>.</p>\n</li>\n</ol>\n<p>[SCREENSHOT: attributes_catalog_definitions.png]</p>\n<p>[SCREENSHOT: attributes_user_values.png]</p>\n<p><strong>2b. Use the attribute in the predicate.</strong> Write the full attribute name in double curly braces:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"predicate\": \"\\\"bank\\\".\\\"core\\\".\\\"branches\\\".\\\"city\\\" = {{bank.city}}\",\n  \"scope\": [\"\\\"bank\\\".\\\"core\\\".\\\"branches\\\"\"]\n}\n\n</code></pre>\n<p>When Dana runs a query, PVML replaces <code>{{bank.city}}</code> with <code>'London'</code>. When Marc runs the same query, it becomes <code>'Paris'</code>. One policy, many managers.</p>\n<p>💡 If Dana moves to Paris, just change her attribute. You do not need to change the policy.</p>\n<hr />\n<h3 id=\"step-3-filter-related-tables\">Step 3: Filter related tables</h3>\n<p>The <code>accounts</code> and <code>transactions</code> tables have no <code>city</code> column. How can we filter them by city?</p>\n<p>PVML can follow <strong>relations</strong> between tables (like foreign keys):</p>\n<ul>\n<li><p><code>accounts.branch_id</code> points to <code>branches.branch_id</code></p>\n</li>\n<li><p><code>transactions.account_id</code> points to <code>accounts.account_id</code></p>\n</li>\n</ul>\n<p><strong>3a. Tell PVML about the relations</strong> with <strong>Update Relations</strong> (see <strong>Guide: Relations and identifiers</strong>):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"schema\": \"\\\"bank\\\".\\\"core\\\"\",\n  \"relations\": [\n    {\n      \"referencingTable\": \"\\\"accounts\\\"\",\n      \"referencedTable\": \"\\\"branches\\\"\",\n      \"columnsReferences\": [{\"referencingColumn\": \"\\\"branch_id\\\"\", \"referencedColumn\": \"\\\"branch_id\\\"\"}]\n    },\n    {\n      \"referencingTable\": \"\\\"transactions\\\"\",\n      \"referencedTable\": \"\\\"accounts\\\"\",\n      \"columnsReferences\": [{\"referencingColumn\": \"\\\"account_id\\\"\", \"referencedColumn\": \"\\\"account_id\\\"\"}]\n    }\n  ]\n}\n\n</code></pre>\n<p>[SCREENSHOT: relations.png]</p>\n<p><strong>3b. Add the related tables to the scope:</strong></p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"predicate\": \"\\\"bank\\\".\\\"core\\\".\\\"branches\\\".\\\"city\\\" = {{bank.city}}\",\n  \"scope\": [\n    \"\\\"bank\\\".\\\"core\\\".\\\"branches\\\"\",\n    \"\\\"bank\\\".\\\"core\\\".\\\"accounts\\\"\",\n    \"\\\"bank\\\".\\\"core\\\".\\\"transactions\\\"\"\n  ]\n}\n\n</code></pre>\n<p>[SCREENSHOT: rls_scope.png]</p>\n<p>Now Dana sees only London branches, the accounts of London branches, and the transactions of those accounts. PVML finds the path <code>transactions → accounts → branches</code> by itself, even across two steps.</p>\n<p>💡 <strong>Only the tables in the scope are filtered.</strong> A related table that is not in the scope shows all its rows. For example, if the scope is only <code>transactions</code>, Dana still sees all accounts.</p>\n<p>⚠️ Every table in the scope needs a relation path to the predicate table, and all tables must be in the same schema. Add missing relations first.</p>\n<hr />\n<h3 id=\"step-4-other-attribute-types\">Step 4: Other attribute types</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>You want</th>\n<th>Attribute type and value</th>\n<th>Predicate</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>One text value</td>\n<td><code>text</code>: <code>\"London\"</code></td>\n<td><code>... .\"city\" = {{bank.city}}</code></td>\n</tr>\n<tr>\n<td>A number limit</td>\n<td><code>numeric</code>: <code>50000</code></td>\n<td><code>... .\"income\" &lt;= {{bank.max_income}}</code></td>\n</tr>\n<tr>\n<td>Several values</td>\n<td><code>complex</code>, <code>multiValued: true</code>, item type <code>text</code>: <code>[\"London\", \"Paris\"]</code></td>\n<td><code>... .\"city\" IN ({{bank.cities}})</code></td>\n</tr>\n<tr>\n<td>A field of an object</td>\n<td><code>complex</code> object with fields <code>city</code>, <code>country</code>: <code>{\"city\": \"London\", \"country\": \"GBR\"}</code></td>\n<td><code>... .\"country\" = {{bank.office.country}}</code></td>\n</tr>\n<tr>\n<td>A yes/no flag</td>\n<td><code>boolean</code>: <code>true</code></td>\n<td><code>{{bank.is_head_office}} = true</code></td>\n</tr>\n</tbody>\n</table>\n</div><p>A predicate can be any SQL condition in your database's own SQL, with <code>AND</code>, <code>OR</code>, parentheses, <code>BETWEEN</code>, <code>NOT IN</code>, <code>LIKE</code> and more. An attribute can stand anywhere a value goes, for example <code>... .\"income\" BETWEEN 0 AND {{bank.max_income}}</code>.</p>\n<hr />\n<h3 id=\"step-5-common-patterns\">Step 5: Common patterns</h3>\n<p><strong>The head office sees everything.</strong> Add a boolean attribute <code>is_head_office</code> and use <code>OR</code>:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>\"bank\".\"core\".\"branches\".\"city\" = {{bank.city}} OR {{bank.is_head_office}} = true\n\n</code></pre><p>Managers have <code>is_head_office = false</code> and see their city. Head office users have <code>true</code> and see all rows.</p>\n<p>[SCREENSHOT: rls_row_policy_predicate.png]</p>\n<p><strong>Set the value for a whole team.</strong> Put the attribute on a <strong>group</strong> (with <strong>Set Group Attributes</strong>). All group members get it. If a user also has their own value, the user value wins. The logic: <strong>the more specific value wins</strong>. A value set for one person is more specific than a value set for a whole team.</p>\n<hr />\n<h3 id=\"rules-to-remember\">Rules to remember</h3>\n<ol>\n<li><p><strong>No value, no data.</strong> If a user has no value for an attribute that a policy needs, their query is blocked. The logic: when PVML is not sure, it says no. A missing value never opens more data.</p>\n</li>\n<li><p><strong>The attribute must exist</strong> before you create the policy.</p>\n</li>\n<li><p><strong>Several row policies on the same table work together with AND.</strong> Each row policy is a limit, and all limits apply. A row is visible only if it passes all of them. If you need OR, write one predicate with <code>OR</code>. See <strong>Guide: How policies work together</strong>.</p>\n</li>\n<li><p><strong>Row policies do not hide columns.</strong> Use a column policy or masking for that.</p>\n</li>\n</ol>\n<hr />\n<h3 id=\"test-your-policy\">Test your policy</h3>\n<ol>\n<li><p>Give a test user the attribute values you want to check.</p>\n</li>\n<li><p>Run a query as this user, for example with <strong>Execute Query on Virtual DB</strong>.</p>\n</li>\n<li><p>Open the query in <strong>Get Audit Record</strong>. The <code>permissions</code> field shows the exact filter PVML applied (<code>filterPermissionPaths</code>).</p>\n</li>\n</ol>\n","_postman_id":"245d231b-5506-4e0c-998b-597d3687f14d","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Guide: How policies work together","item":[],"id":"2b1e2875-efee-4fd1-9bfb-9dc981cd83c2","description":"<p>A VDB usually has several policies. This guide explains what happens when they meet. The examples use the bank tables from <strong>Guide: Row-level security (RLS)</strong>.</p>\n<h3 id=\"the-logic-behind-it\">The logic behind it</h3>\n<p>PVML follows two simple rules. If you know them, you can guess the result of any combination.</p>\n<ol>\n<li><strong>Grants add up.</strong> Column, write and routines policies <strong>give</strong> access. Each one adds more, like keys on a key ring.</li>\n<li><strong>Limits stack, and the strictest one wins.</strong> Row filters, masking, row limits, timeouts and Privacy policies <strong>take away</strong> access. All of them apply together. When two limits disagree, PVML always picks the <strong>safer</strong> result.</li>\n</ol>\n<p>Security comes first: PVML never solves a conflict by showing more data.</p>\n<p>[SCREENSHOT: vdb_policy_tabs.png]</p>\n<h3 id=\"quick-summary\">Quick summary</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Policy type</th>\n<th>Several policies in one VDB</th>\n<th>Why</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Column (read access)</td>\n<td><strong>Union</strong>: users see every column that <strong>any</strong> column policy allows</td>\n<td>A grant: grants add up</td>\n</tr>\n<tr>\n<td>Write</td>\n<td><strong>Union</strong></td>\n<td>A grant</td>\n</tr>\n<tr>\n<td>Routines</td>\n<td><strong>Union</strong></td>\n<td>A grant</td>\n</tr>\n<tr>\n<td>Row (filter)</td>\n<td><strong>AND</strong>: a row is visible only if it passes <strong>all</strong> row policies on that table</td>\n<td>A limit: all limits apply</td>\n</tr>\n<tr>\n<td>Masking, same method on a column</td>\n<td>That method is used</td>\n<td>No conflict</td>\n</tr>\n<tr>\n<td>Masking, different methods on a column</td>\n<td><strong>Full mask</strong> <code>*****</code></td>\n<td>A conflict: the safest result wins</td>\n</tr>\n<tr>\n<td>Row limit</td>\n<td><strong>The smallest</strong> <code>maxRows</code></td>\n<td>A conflict: the safest result wins</td>\n</tr>\n<tr>\n<td>Timeout</td>\n<td>Attach only one per VDB</td>\n<td>Keeps the limit clear</td>\n</tr>\n</tbody>\n</table>\n</div><h3 id=\"column-policies-add-up-grants\">Column policies add up (grants)</h3>\n<p>Policy A allows <code>accounts.account_id</code>. Policy B allows <code>accounts.income</code>. A VDB with both:</p>\n<ul>\n<li><code>SELECT account_id, income FROM accounts</code> works.</li>\n<li><code>SELECT holder_name FROM accounts</code> is blocked.</li>\n<li><code>SELECT * FROM accounts</code> works and returns <strong>only</strong> <code>account_id</code> and <code>income</code>.</li>\n</ul>\n<p>💡 A VDB needs at least one column policy. Without it, users see no data at all.</p>\n<h3 id=\"row-policies-narrow-each-other-limits\">Row policies narrow each other (limits)</h3>\n<p>Each row policy is a limit, so all of them apply.</p>\n<p>Policy A: <code>country = 'GBR'</code>. Policy B: <code>is_business = true</code>. A VDB with both shows only <strong>UK business</strong> accounts. It does <strong>not</strong> show all UK accounts plus all business accounts.</p>\n<p>If you want \"UK accounts <strong>or</strong> business accounts\", write <strong>one</strong> policy:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>\"bank\".\"core\".\"accounts\".\"country\" = 'GBR' OR \"bank\".\"core\".\"accounts\".\"is_business\" = true\n</code></pre><h3 id=\"two-masking-policies-on-one-column\">Two masking policies on one column</h3>\n<p>If two masking policies use the same method on a column, that method is used. If they use different methods (for example partial and hash), PVML can not know which one you meant. So it picks the safest result: the full mask <code>*****</code>.</p>\n<h3 id=\"row-filters-and-masking-together\">Row filters and masking together</h3>\n<p>Both are limits, so both apply. Dana sees only London accounts, and inside them the names are masked.</p>\n<h3 id=\"privacy-differential-privacy-and-aggregation-functions-only\">Privacy: Differential Privacy and Aggregation Functions Only</h3>\n<ul>\n<li><strong>Differential Privacy</strong> turns results of the schema into noisy aggregates. Non-aggregate queries (<code>SELECT *</code>) are blocked.</li>\n<li><strong>Aggregation Functions Only</strong> also blocks non-aggregate queries, but returns exact results.</li>\n<li>Use Aggregation Functions Only when exact totals are fine and you only want to stop row-level exports. Use Differential Privacy when even exact totals could reveal one person.</li>\n</ul>\n<h3 id=\"load-control-timeout-and-row-limit\">Load Control: timeout and row limit</h3>\n<ul>\n<li><strong>Timeout</strong> fails the query when it runs too long.</li>\n<li><strong>Row limit</strong> does not fail the query. It returns only the first rows.</li>\n</ul>\n<p>If several row limits apply, the smallest one wins: the safest result. Attach only one timeout policy to a VDB, so the limit is clear.</p>\n","_postman_id":"2b1e2875-efee-4fd1-9bfb-9dc981cd83c2","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Guide: Masking strategies","item":[],"id":"dc91bf15-3305-4208-a7a0-cc996420be9b","description":"<p>Masking hides the values of a column in query results. The data in your database does not change. PVML has four masking methods.</p>\n<h3 id=\"the-four-methods\">The four methods</h3>\n<p>Example column <code>holder_name</code>, value <code>Johnson</code>:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Method</th>\n<th>Body fields</th>\n<th>Result</th>\n<th>Good for</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Full</td>\n<td><code>\"unmaskLeft\": 0, \"unmaskRight\": 0</code></td>\n<td><code>*****</code></td>\n<td>Values nobody should see</td>\n</tr>\n<tr>\n<td>Partial</td>\n<td><code>\"unmaskLeft\": 2</code></td>\n<td><code>Jo*****</code></td>\n<td>Keep a hint, for example the last 4 digits of a card (<code>\"unmaskRight\": 4</code>)</td>\n</tr>\n<tr>\n<td>Hash</td>\n<td><code>\"isHash\": true</code></td>\n<td>a fixed code, for example <code>c51e…</code></td>\n<td>Count, group and join without seeing the value</td>\n</tr>\n<tr>\n<td>Regex</td>\n<td><code>\"regex\": \"[aeiou]\"</code></td>\n<td><code>J***hns***n</code></td>\n<td>Hide only parts of a value that match a pattern</td>\n</tr>\n</tbody>\n</table>\n</div><p>Each item in the policy <code>data</code> has the <code>column</code> (full path) plus the fields of one method. One policy can use different methods for different columns:</p>\n<p>[SCREENSHOT: masking_methods.png]</p>\n<h3 id=\"full-and-partial\">Full and partial</h3>\n<ul>\n<li>Full masking always returns <code>*****</code>, whatever the length of the value.</li>\n<li>Partial masking keeps <code>unmaskLeft</code> characters at the start and <code>unmaskRight</code> at the end. The rest becomes <code>*****</code>.</li>\n</ul>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">[{\"column\": \"\\\"bank\\\".\\\"core\\\".\\\"accounts\\\".\\\"card_number\\\"\", \"unmaskLeft\": 0, \"unmaskRight\": 4}]\n</code></pre>\n<h3 id=\"hash-masked-but-still-useful\">Hash: masked, but still useful</h3>\n<p>The same value always gives the same code. So users can still:</p>\n<ul>\n<li>count distinct customers: <code>COUNT(DISTINCT holder_name)</code></li>\n<li>group by the column: <code>GROUP BY holder_name</code></li>\n<li>join two tables on it: <code>JOIN ... ON a.holder_name = b.holder_name</code></li>\n</ul>\n<p>…without seeing a single real name.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">[{\"column\": \"\\\"bank\\\".\\\"core\\\".\\\"accounts\\\".\\\"holder_name\\\"\", \"isHash\": true, \"unmaskLeft\": 0, \"unmaskRight\": 0}]\n</code></pre>\n<h3 id=\"regex-mask-only-part-of-the-value\">Regex: mask only part of the value</h3>\n<p>PVML replaces each part that matches the pattern with <code>***</code>. Some useful patterns:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Goal</th>\n<th>Pattern</th>\n<th>Example</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Hide the user part of an email</td>\n<td><code>^[^@]+</code></td>\n<td><code>dana@example.com</code> → <code>***@example.com</code></td>\n</tr>\n<tr>\n<td>Hide a card number except the last 4 digits</td>\n<td><code>^[0-9]{4}-[0-9]{4}-[0-9]{4}</code></td>\n<td><code>4532-0151-1283-0366</code> → <code>***-0366</code></td>\n</tr>\n<tr>\n<td>Hide all digits</td>\n<td><code>[0-9]+</code></td>\n<td><code>192.168.1.100</code> → <code>***.***.***.***</code></td>\n</tr>\n</tbody>\n</table>\n</div><p>💡 In JSON, escape a backslash as <code>\\\\</code>. For example the pattern <code>\\d+</code> is written <code>\"\\\\d+\"</code>.</p>\n<p>⚠️ MSSQL supports regex masking only from SQL Server 2025.</p>\n<h3 id=\"masked-columns-in-functions\">Masked columns in functions</h3>\n<p>If a query uses a masked column inside a function, the result is also masked. For example <code>UPPER(holder_name)</code> returns <code>*****</code>. The logic: a function must not become a way around the mask. With hash masking, the hashed value is used, so <code>COUNT(DISTINCT ...)</code> and joins still work.</p>\n<h3 id=\"remember\">Remember</h3>\n<ul>\n<li>If two masking policies use different methods on one column, the result is the full mask: in a conflict, PVML picks the safest result.</li>\n</ul>\n","_postman_id":"dc91bf15-3305-4208-a7a0-cc996420be9b","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Guide: Differential Privacy","item":[],"id":"3d2343e0-ec34-4d8f-81ea-33eece429922","description":"<p><strong>Differential Privacy (DP)</strong> lets people learn from sensitive data without learning about any single person in it. Analysts, partners and AI clients get totals, averages and trends. Each individual in the data stays protected, with a mathematical guarantee.</p>\n<p>With PVML you get this on your <strong>live</strong> data: no copy, no synthetic dataset, no change to the database.</p>\n<h3 id=\"who-uses-differential-privacy\">Who uses Differential Privacy</h3>\n<p>DP is a proven method, used by large public bodies and tech companies:</p>\n<ul>\n<li><strong>U.S. Census Bureau.</strong> By law (Title 13), the Bureau must keep every answer confidential. In an internal test, it rebuilt records of more than 300 million people only from the published 2010 tables. With older methods, the data would have needed so much noise that it would be too inaccurate for most uses. So the Bureau used DP for the <strong>2020 Census</strong>, starting with the redistricting data that states use to draw voting districts. DP-protected data can be shared and combined with other data with no added risk, also against attacks that do not exist yet (<a href=\"https://www2.census.gov/library/publications/decennial/2020/census-briefs/c2020br-03.pdf\">Link</a>). Its first product with formal privacy protection was <strong>OnTheMap</strong>, which shows where workers live and work (<a href=\"https://www.census.gov/content/dam/Census/library/working-papers/2020/adrm/The%20modernization%20of%20statistical%20disclosure%20limitation%20at%20the%20U.S.%20Census%20Bureau.pdf\">Link</a>).</li>\n<li><strong>Google</strong> published <strong>COVID-19 Community Mobility Reports</strong> (2020) with DP. Public health teams saw how movement changed in each region, without data about any one person. (<a href=\"https://www.google.com/covid19/mobility/\">Link</a>)</li>\n<li><strong>Apple</strong> uses DP to learn which emoji and QuickType words people use, to improve suggestions for everyone. (<a href=\"https://www.apple.com/privacy/docs/Differential_Privacy_Overview.pdf\">Link</a>)</li>\n<li><strong>LinkedIn</strong> gives marketers audience engagement insights through its <strong>Audience Engagements API</strong>, protected with DP. (<a href=\"https://arxiv.org/abs/2002.05839\">Link</a>)</li>\n<li><strong>Microsoft</strong> collects usage data from millions of devices with DP (<a href=\"https://www.microsoft.com/en-us/research/publication/collecting-telemetry-data-privately/\">Link</a>), and published a <strong>U.S. broadband usage</strong> dataset by zip code with DP to help close the rural broadband gap (<a href=\"https://github.com/microsoft/USBroadbandUsagePercentages\">Link</a>).</li>\n</ul>\n<p>PVML brings the same method to your own databases, as an <strong>off-the-shelf policy</strong>. You choose the schema, and PVML applies DP <strong>at runtime</strong>, to every query. No data science team, no changes to your data or your queries, zero effort on your end.</p>\n<h3 id=\"how-it-works-in-pvml\">How it works in PVML</h3>\n<p>You put a DP policy on a schema. From then on, in every VDB with this policy:</p>\n<ul>\n<li><strong>Aggregate queries work</strong> (counts, sums, averages and more). PVML adds a small amount of random noise to each result, in real time.</li>\n<li><strong>For large groups, the noise is tiny.</strong> The result is close to the true value and good for analysis.</li>\n<li><strong>One person can not be singled out.</strong> Results that would describe one person are not returned.</li>\n</ul>\n<h3 id=\"example-plan-a-campaign-without-seeing-customers\">Example: plan a campaign without seeing customers</h3>\n<p>The marketing team of a bank wants to know, per country, how many customers it has and their average income. They must never see the data of one customer.</p>\n<p>The admin puts a DP policy on the <code>\"bank\".\"core\"</code> schema. A marketing analyst runs:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>SELECT country, COUNT(*), AVG(income)\nFROM \"bank\".\"core\".\"accounts\"\nGROUP BY country\n</code></pre><div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>country</th>\n<th>True count</th>\n<th>DP count</th>\n<th>True avg income</th>\n<th>DP avg income</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>AUT</td>\n<td>461</td>\n<td>461.0</td>\n<td>36,132</td>\n<td>36,173</td>\n</tr>\n<tr>\n<td>AZE</td>\n<td>423</td>\n<td>423.4</td>\n<td>33,844</td>\n<td>33,915</td>\n</tr>\n<tr>\n<td>DEU</td>\n<td>443</td>\n<td>442.6</td>\n<td>34,465</td>\n<td>34,340</td>\n</tr>\n<tr>\n<td>ESP</td>\n<td>409</td>\n<td>408.6</td>\n<td>34,672</td>\n<td>34,627</td>\n</tr>\n</tbody>\n</table>\n</div><p>The analyst sees the same picture as with the true data, and can plan the campaign. The analyst only sees the DP columns. The true values are shown here only to compare.</p>\n<h3 id=\"what-you-can-run\">What you can run</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>You can use</th>\n<th>Example</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>COUNT(*)</code>, <code>COUNT(DISTINCT ...)</code></td>\n<td><code>SELECT COUNT(DISTINCT account_id) FROM accounts</code></td>\n</tr>\n<tr>\n<td><code>SUM</code>, <code>AVG</code></td>\n<td><code>SELECT SUM(income), AVG(income) FROM accounts</code></td>\n</tr>\n<tr>\n<td><code>MIN</code>, <code>MAX</code></td>\n<td><code>SELECT MIN(income), MAX(income) FROM accounts</code></td>\n</tr>\n<tr>\n<td><code>WHERE</code></td>\n<td><code>... WHERE is_business = true</code></td>\n</tr>\n<tr>\n<td><code>GROUP BY</code></td>\n<td><code>... GROUP BY country</code></td>\n</tr>\n<tr>\n<td><code>HAVING</code></td>\n<td><code>... GROUP BY country HAVING COUNT(*) &gt; 440</code></td>\n</tr>\n<tr>\n<td><code>JOIN</code> of related tables</td>\n<td><code>SUM(t.amount)</code> per country, from <code>transactions</code> joined to <code>accounts</code></td>\n</tr>\n<tr>\n<td>Functions on the result</td>\n<td><code>SELECT ROUND(AVG(income)) FROM accounts</code></td>\n</tr>\n</tbody>\n</table>\n</div><p>Queries that return single rows, like <code>SELECT * FROM accounts</code> or a list of account IDs, are not allowed. A query about one account returns no result.</p>\n<h3 id=\"set-it-up\">Set it up</h3>\n<ol>\n<li><strong>Standard snapshot.</strong> DP needs the data samples of a Standard snapshot (see <strong>Refresh Snapshot</strong>).</li>\n<li><strong>Identifier.</strong> Tell PVML who the \"person\" is, for example <code>accounts.account_id</code> (<strong>Update Identifiers</strong>).</li>\n<li><strong>Relations.</strong> Link the other tables to the identifier, for example <code>transactions.account_id</code> to <code>accounts.account_id</code> (<strong>Update Relations</strong>). Then queries on <code>transactions</code> are protected per account too.</li>\n<li><strong>DP policy.</strong> Create it on the schema (<strong>Create DP Policy</strong>). In the platform: <strong>Policies &gt; Privacy &gt; Differential Privacy</strong>.</li>\n<li><strong>VDB.</strong> Attach it to a VDB, together with a column policy (<strong>Create Virtual DB</strong> or <strong>Update Virtual DB Policies</strong>).</li>\n</ol>\n<p>📘 More on identifiers and relations: <strong>Datasources &gt; Guide: Relations and identifiers</strong>.</p>\n<h3 id=\"tips-for-great-results\">Tips for great results</h3>\n<ul>\n<li><strong>Ask about groups, not people.</strong> DP works best on large datasets (roughly 10,000 rows or more) and on groups with many people, for example per country rather than per street.</li>\n<li><strong>Round for reports.</strong> <code>ROUND(AVG(income))</code> gives clean numbers for dashboards.</li>\n<li><strong>Exact totals are fine?</strong> Use <strong>Aggregation Functions Only</strong> instead. It also allows only aggregate queries, but returns exact results. See <strong>Guide: How policies work together</strong>.</li>\n<li><strong>AI clients get the same protection.</strong> An AI client connected to the VDB through MCP can answer questions like \"average income per country\", and every answer is protected by DP.</li>\n</ul>\n","_postman_id":"3d2343e0-ec34-4d8f-81ea-33eece429922","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Guide: Writing object names","item":[],"id":"7769d699-3148-4f0b-a19d-0e973818f14f","description":"<p>Policies, relations, identifiers and descriptions all name database objects. PVML needs the <strong>full path</strong> with the <strong>right quotes</strong> for your database. This is the most common reason for errors.</p>\n<h3 id=\"the-easy-way\">The easy way</h3>\n<p>Copy the names from <strong>Get Snapshot</strong>. It returns every schema, table and column exactly as PVML expects them, with quotes.</p>\n<p>A policy must protect exactly the object you mean, so names must match exactly: same path, same case.</p>\n<h3 id=\"full-paths\">Full paths</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Object</th>\n<th>Format</th>\n<th>Example (Postgres)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Schema</td>\n<td><code>database.schema</code></td>\n<td><code>\"bank\".\"core\"</code></td>\n</tr>\n<tr>\n<td>Table</td>\n<td><code>database.schema.table</code></td>\n<td><code>\"bank\".\"core\".\"accounts\"</code></td>\n</tr>\n<tr>\n<td>Column</td>\n<td><code>database.schema.table.column</code></td>\n<td><code>\"bank\".\"core\".\"accounts\".\"income\"</code></td>\n</tr>\n</tbody>\n</table>\n</div><p>Some request bodies split the path into fields (for example <code>schema</code> and <code>table</code> in a column policy, or <code>schema</code> and <code>tableName</code> in relations). Then each field holds only its own part, with quotes: <code>\"schema\": \"\\\"bank\\\".\\\"core\\\"\"</code>, <code>\"table\": \"\\\"accounts\\\"\"</code>.</p>\n<h3 id=\"quotes-and-the-first-part-per-database\">Quotes and the first part, per database</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Database</th>\n<th>Quote</th>\n<th>First part</th>\n<th>Column example</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Postgres</td>\n<td><code>\"</code></td>\n<td>database</td>\n<td><code>\"bank\".\"core\".\"accounts\".\"income\"</code></td>\n</tr>\n<tr>\n<td>Redshift</td>\n<td><code>\"</code></td>\n<td>database</td>\n<td><code>\"bank\".\"core\".\"accounts\".\"income\"</code></td>\n</tr>\n<tr>\n<td>Snowflake</td>\n<td><code>\"</code></td>\n<td>database</td>\n<td><code>\"BANK\".\"CORE\".\"ACCOUNTS\".\"INCOME\"</code></td>\n</tr>\n<tr>\n<td>MSSQL</td>\n<td><code>\"</code> (or <code>[ ]</code>)</td>\n<td>database</td>\n<td><code>\"bank\".\"dbo\".\"accounts\".\"income\"</code></td>\n</tr>\n<tr>\n<td>Trino</td>\n<td><code>\"</code></td>\n<td>catalog</td>\n<td><code>\"hive\".\"core\".\"accounts\".\"income\"</code></td>\n</tr>\n<tr>\n<td>Databricks</td>\n<td><code>`</code></td>\n<td>catalog</td>\n<td><code>`main`.`core`.`accounts`.`income`</code></td>\n</tr>\n<tr>\n<td>BigQuery</td>\n<td><code>`</code></td>\n<td>project</td>\n<td><code>`my-project`.`core`.`accounts`.`income`</code></td>\n</tr>\n<tr>\n<td>MySQL</td>\n<td><code>`</code></td>\n<td>(none, the database is the schema)</td>\n<td><code>`bank`.`accounts`.`income`</code></td>\n</tr>\n</tbody>\n</table>\n</div><h3 id=\"in-json\">In JSON</h3>\n<p>The policy <code>data</code> field is a JSON <strong>string</strong>, so the quotes inside it are escaped:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">\"data\": \"[{\\\"column\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\".\\\\\\\"accounts\\\\\\\".\\\\\\\"income\\\\\\\"\\\", \\\"unmaskLeft\\\": 0, \\\"unmaskRight\\\": 0}]\"\n</code></pre>\n<p>This is hard to write by hand. Tip: build the inner JSON as an object in your code, then convert it to a string (for example <code>JSON.stringify</code> or <code>json.dumps</code>). Databases with backticks (BigQuery, Databricks, MySQL) need no escaping inside the string.</p>\n<h3 id=\"case\">Case</h3>\n<p>Write names with the <strong>same upper and lower case as in the snapshot</strong>. Inside quotes, <code>\"Accounts\"</code> and <code>\"accounts\"</code> are different names. Snowflake often stores names in upper case.</p>\n<h3 id=\"in-sql-queries\">In SQL queries</h3>\n<p>Users who run queries can use the normal names of their database. The full path rule is only for policy and metadata bodies.</p>\n","_postman_id":"7769d699-3148-4f0b-a19d-0e973818f14f","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Get Policies","id":"7bfdcda0-d9f7-48a9-86b4-650e915c205d","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/permissions?type=data_privacy","description":"<p>Returns all the different policies associated with a specific workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string, optional): Filters the permissions to a specific type. Options:</p>\n<ul>\n<li><p><strong>data_access</strong>: column policy</p>\n</li>\n<li><p><strong>data_filter</strong>: row policy</p>\n</li>\n<li><p><strong>data_privacy</strong>: Differential Privacy (DP) policy</p>\n</li>\n<li><p><strong>data_masking</strong>: dynamic masking policy</p>\n</li>\n<li><p><strong>data_validation</strong>: Aggregation Functions Only (platform: Privacy tab)</p>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p><strong><code>permissions</code></strong>: Array of permission objects, each containing:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): Unique identifier for the policy.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): Name of the policy.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): Description of the policy (can be empty).</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): Type of policy (e.g., <code>data_access</code>).</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): Identifier of the associated workspace.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): JSON string detailing schema, table, and column-level access.</p>\n</li>\n<li><p><strong><code>createdAt</code></strong> (string): ISO 8601 timestamp of policy creation.</p>\n</li>\n<li><p><strong><code>lastModifiedAt</code></strong> (string): ISO 8601 timestamp of last modification.</p>\n</li>\n<li><p><strong><code>datasourceId</code></strong> (string): ID of the data source.</p>\n</li>\n<li><p><strong><code>datasourceName</code></strong> (string): Name of the data source.</p>\n</li>\n<li><p><strong><code>datasourceType</code></strong> (string): Type of data source (e.g., <code>Postgres</code>).</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","permissions"],"host":["platform","pvml","com"],"query":[{"key":"type","value":"data_privacy"}],"variable":[]}},"response":[{"id":"ca7376d8-5492-4e7b-8174-3cfdf425fc47","name":"Get Policies - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/permissions?type=data_privacy","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","permissions"],"query":[{"key":"type","value":"data_privacy"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permissions\": [\n        {\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceName\": \"MyDB\",\n            \"datasourceType\": \"BigQuery\",\n            \"id\": \"99727aab-d62d-59f2-8cea-6bc23db7c0df\",\n            \"name\": \"doctors core details\",\n            \"description\": \"access only to doctors' id and name columns\",\n            \"type\": \"data_access\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"data\": \"[{\\\"table\\\": \\\"`doctors`\\\", \\\"column\\\": \\\"`id`\\\", \\\"schema\\\": \\\"`my-project`.`clinic`\\\"}, {\\\"table\\\": \\\"`doctors`\\\", \\\"column\\\": \\\"`name`\\\", \\\"schema\\\": \\\"`my-project`.`clinic`\\\"}]\",\n            \"createdAt\": \"2026-10-29T23:55:57.023059Z\",\n            \"lastModifiedAt\": \"2026-10-29T23:55:57.023059Z\"\n        },\n        {\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceName\": \"MyDB\",\n            \"datasourceType\": \"BigQuery\",\n            \"id\": \"ac2ffc71-740d-5e6f-b54e-75ad0e3dce3e\",\n            \"name\": \"female patients only\",\n            \"description\": \"\",\n            \"type\": \"data_filter\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"data\": \"{\\\"scope\\\": [\\\"`my-project`.`clinic`.`patients`\\\"], \\\"columns\\\": [\\\"`my-project`.`clinic`.`patients`.`gender`\\\"], \\\"predicate\\\": \\\"`my-project`.`clinic`.`patients`.`gender`='F'\\\"}\",\n            \"createdAt\": \"2026-10-29T23:55:57.562049Z\",\n            \"lastModifiedAt\": \"2026-10-29T23:55:57.562049Z\"\n        },\n        {\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceName\": \"MyDB\",\n            \"datasourceType\": \"BigQuery\",\n            \"id\": \"f2214a61-7e05-5526-85cd-df7b98635e6d\",\n            \"name\": \"full access\",\n            \"description\": \"access to all schemas, tables and columns\",\n            \"type\": \"data_access\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"data\": \"[{\\\"table\\\": \\\"*\\\", \\\"column\\\": \\\"*\\\", \\\"schema\\\": \\\"*\\\"}]\",\n            \"createdAt\": \"2026-10-29T23:55:56.85125Z\",\n            \"lastModifiedAt\": \"2026-10-29T23:55:56.85125Z\"\n        },\n        {\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceName\": \"MyDB\",\n            \"datasourceType\": \"BigQuery\",\n            \"id\": \"558dba71-52d0-5090-aed5-379aae99f635\",\n            \"name\": \"mask location details\",\n            \"description\": \"\",\n            \"type\": \"data_masking\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"data\": \"[{\\\"regex\\\": \\\"\\\", \\\"column\\\": \\\"`my-project`.`clinic`.`locations`.`latitude`\\\", \\\"isHash\\\": false, \\\"unmaskLeft\\\": 0, \\\"unmaskRight\\\": 0}, {\\\"regex\\\": \\\"\\\", \\\"column\\\": \\\"`my-project`.`clinic`.`locations`.`longitude`\\\", \\\"isHash\\\": false, \\\"unmaskLeft\\\": 0, \\\"unmaskRight\\\": 0}]\",\n            \"createdAt\": \"2026-10-29T23:55:57.933032Z\",\n            \"lastModifiedAt\": \"2026-10-29T23:55:57.933032Z\"\n        },\n        {\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceName\": \"MyDB\",\n            \"datasourceType\": \"BigQuery\",\n            \"id\": \"db0b561c-3dba-5816-9621-0e9c1a2742e1\",\n            \"name\": \"patients aggregates only\",\n            \"description\": \"\",\n            \"type\": \"data_validation\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"data\": \"{\\\"header\\\": \\\"aggregation\\\", \\\"aggregationPermission\\\": {\\\"tables\\\": [\\\"`my-project`.`clinic`.`patients`\\\"], \\\"schemas\\\": []}}\",\n            \"createdAt\": \"2026-10-29T23:55:57.749851Z\",\n            \"lastModifiedAt\": \"2026-10-29T23:55:57.749851Z\"\n        },\n        {\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceName\": \"MyDB\",\n            \"datasourceType\": \"BigQuery\",\n            \"id\": \"dff14d57-617b-5a67-9328-887760b4ec3a\",\n            \"name\": \"private schema\",\n            \"description\": \"\",\n            \"type\": \"data_privacy\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"data\": \"{\\\"schemas\\\": [\\\"`my-project`.`clinic`\\\"]}\",\n            \"createdAt\": \"2026-10-29T23:55:58.124131Z\",\n            \"lastModifiedAt\": \"2026-10-29T23:55:58.124131Z\"\n        }\n    ]\n}"}],"_postman_id":"7bfdcda0-d9f7-48a9-86b4-650e915c205d"},{"name":"Create Column Policy","id":"d2a9217f-d8fa-4c75-a75d-af6371ba1468","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"transactions access test2\",\n    \"description\": \"access to transactions columns and tables\",\n    \"type\": \"data_access\",\n    \"data\": \"[{\\\"table\\\": \\\"*\\\", \\\"column\\\": \\\"*\\\", \\\"schema\\\": \\\"*\\\"}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>This endpoint allows to create a column policy for a specific datasource, without explicitly choosing the structure (using \"*\" as a <em>select all</em> mechanism).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>datasource_id</code></strong>(string): The unique id of the datasource for this policy.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>Form data containing:</p>\n<ul>\n<li><p><strong><code>name</code></strong>(string): The name of the new policy.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A short description.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of the new policy. Options:</p>\n<ul>\n<li><p><strong>data_access</strong>: column policy</p>\n</li>\n<li><p><strong>data_filter</strong>: row policy</p>\n</li>\n<li><p><strong>data_privacy</strong>: Differential Privacy (DP) policy</p>\n</li>\n<li><p><strong>data_masking</strong>: dynamic masking policy</p>\n</li>\n<li><p><strong>data_validation</strong>: Aggregation Functions Only (platform: Privacy tab)</p>\n</li>\n</ul>\n</li>\n<li><p><strong><code>data</code></strong> (json): Json containing the relevant schema tree structure:</p>\n<ul>\n<li><p><strong>schema(s)</strong>: Specifies the schema(s) involved.</p>\n</li>\n<li><p><strong>table(s)</strong>: Identifies one or more tables within the schema(s) to which access is granted.</p>\n</li>\n<li><p><strong>column(s)</strong>: Lists the specific columns from each table to which access is granted.</p>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p>The response is a <strong><code>permission</code></strong> object in json format that includes the details of the newly created policy:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): Unique identifier for the policy.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): Name of the policy, describing its purpose or scope.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A detailed description of what the policy allows.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of policy, e.g., \"data_access\".</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): Identifier for the workspace to which the policy belongs.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): JSON-encoded string representing the schema, table, and columns to which access is granted.</p>\n<ul>\n<li><p>Each element in the JSON array represents an access object with the following fields:</p>\n<ul>\n<li><p><strong>table</strong> (string): The name of the database table.</p>\n</li>\n<li><p><strong>column</strong> (string): The specific column in the table that is accessible.</p>\n</li>\n<li><p><strong>schema</strong> (string): The schema path for the table within the database.</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p><strong><code>createdAt</code></strong> (timestamp, ISO 8601): Timestamp indicating when the policy was created.</p>\n</li>\n<li><p><strong><code>lastModifiedAt</code></strong> (timestamp, ISO 8601): Timestamp indicating when the policy was last modified.</p>\n</li>\n<li><p><strong><code>datasourceId</code></strong> (string): Identifier for the data source related to the policy.</p>\n</li>\n<li><p><strong><code>datasourceName</code></strong> (string): Name of the data source related to the policy.</p>\n</li>\n<li><p><strong><code>datasourceType</code></strong> (string): The type of the data source, e.g., Postgres.</p>\n</li>\n</ul>\n<p>💡 Names must be full paths with the right quotes. See <strong>Guide: Writing object names</strong>.</p>\n<h4 id=\"how-to-select-a-whole-schema-or-table\">How to select a whole schema or table</h4>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>You want</th>\n<th>Item in <code>data</code></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Everything in the datasource</td>\n<td><code>{\"schema\": \"*\", \"table\": \"*\", \"column\": \"*\"}</code></td>\n</tr>\n<tr>\n<td>A whole schema</td>\n<td><code>{\"schema\": \"&lt;schema&gt;\", \"table\": \"\", \"column\": \"\"}</code></td>\n</tr>\n<tr>\n<td>A whole table</td>\n<td><code>{\"schema\": \"&lt;schema&gt;\", \"table\": \"&lt;table&gt;\", \"column\": \"\"}</code></td>\n</tr>\n<tr>\n<td>One column</td>\n<td><code>{\"schema\": \"&lt;schema&gt;\", \"table\": \"&lt;table&gt;\", \"column\": \"&lt;column&gt;\"}</code></td>\n</tr>\n</tbody>\n</table>\n</div><p>💡 To mean \"all\" for one part, use an empty string (<code>\"\"</code>).</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"74b28067-3ce7-4797-a063-7a1d5b1f0c15","name":"Create Column Policy - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"transactions access test2\",\n    \"description\": \"access to transactions columns and tables\",\n    \"type\": \"data_access\",\n    \"data\": \"[{\\\"table\\\": \\\"*\\\", \\\"column\\\": \\\"*\\\", \\\"schema\\\": \\\"*\\\"}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"f2214a61-7e05-5526-85cd-df7b98635e6d\",\n        \"name\": \"full access\",\n        \"description\": \"access to all schemas, tables and columns\",\n        \"type\": \"data_access\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"data\": \"[{\\\"table\\\": \\\"*\\\", \\\"column\\\": \\\"*\\\", \\\"schema\\\": \\\"*\\\"}]\",\n        \"createdAt\": \"2026-10-29T23:55:56.85125Z\",\n        \"lastModifiedAt\": \"2026-10-29T23:55:56.85125Z\"\n    }\n}"}],"_postman_id":"d2a9217f-d8fa-4c75-a75d-af6371ba1468"},{"name":"Create Column Policy - Explicit","id":"bb1dd34c-c1e9-40bc-b5b6-824765410573","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"doctors core details\",\n    \"description\": \"access only to doctors' id and name columns\",\n    \"type\": \"data_access\",\n    \"data\": \"[{\\\"schema\\\": \\\"`my-project`.`clinic`\\\", \\\"table\\\": \\\"`doctors`\\\", \\\"column\\\": \\\"`id`\\\"}, {\\\"schema\\\": \\\"`my-project`.`clinic`\\\", \\\"table\\\": \\\"`doctors`\\\", \\\"column\\\": \\\"`name`\\\"}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>This endpoint allows to create a column policy for a specific datasource with explicit selection of the database structure.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>datasource_id</code></strong>(string): The unique id of the datasource for this policy.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>Form data containing:</p>\n<ul>\n<li><p><strong><code>name</code></strong>(string): The name of the new policy.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): The name of the new workspace.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of the new policy. Options:</p>\n<ul>\n<li><p><strong>data_access</strong>: column policy</p>\n</li>\n<li><p><strong>data_filter</strong>: row policy</p>\n</li>\n<li><p><strong>data_privacy</strong>: Differential Privacy (DP) policy</p>\n</li>\n<li><p><strong>data_masking</strong>: dynamic masking policy</p>\n</li>\n<li><p><strong>data_validation</strong>: Aggregation Functions Only (platform: Privacy tab)</p>\n</li>\n</ul>\n</li>\n<li><p><strong><code>data</code></strong> (json): Json containing the relevant schema tree structure:</p>\n<ul>\n<li><p><strong>schema(s)</strong>: Specifies the schema(s) involved.</p>\n</li>\n<li><p><strong>table(s)</strong>: Identifies one or more tables within the schema(s) to which access is granted.</p>\n</li>\n<li><p><strong>column(s)</strong>: Lists the specific columns from each table to which access is granted.</p>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p>The response is a <strong><code>permission</code></strong> object in json format that includes the details of the newly created policy:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): Unique identifier for the policy.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): Name of the policy, describing its purpose or scope.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): A detailed description of what the policy allows.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): The type of policy, e.g., \"data_access\".</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): Identifier for the workspace to which the policy belongs.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): JSON-encoded string representing the schema, table, and columns to which access is granted.</p>\n<ul>\n<li><p>Each element in the JSON array represents an access object with the following fields:</p>\n<ul>\n<li><p><strong>table</strong> (string): The name of the database table.</p>\n</li>\n<li><p><strong>column</strong> (string): The specific column in the table that is accessible.</p>\n</li>\n<li><p><strong>schema</strong> (string): The schema path for the table within the database.</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><p><strong><code>createdAt</code></strong> (timestamp, ISO 8601): Timestamp indicating when the policy was created.</p>\n</li>\n<li><p><strong><code>lastModifiedAt</code></strong> (timestamp, ISO 8601): Timestamp indicating when the policy was last modified.</p>\n</li>\n<li><p><strong><code>datasourceId</code></strong> (string): Identifier for the data source related to the policy.</p>\n</li>\n<li><p><strong><code>datasourceName</code></strong> (string): Name of the data source related to the policy.</p>\n</li>\n<li><p><strong><code>datasourceType</code></strong> (string): The type of the data source, e.g., Postgres.</p>\n</li>\n</ul>\n<p>💡 Names must be full paths with the right quotes. See <strong>Guide: Writing object names</strong>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"cf9bb4b3-4286-4616-8e07-f001a480555e","name":"Create Column Policy - Explicit - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"doctors core details\",\n    \"description\": \"access only to doctors' id and name columns\",\n    \"type\": \"data_access\",\n    \"data\": \"[{\\\"schema\\\": \\\"`my-project`.`clinic`\\\", \\\"table\\\": \\\"`doctors`\\\", \\\"column\\\": \\\"`id`\\\"}, {\\\"schema\\\": \\\"`my-project`.`clinic`\\\", \\\"table\\\": \\\"`doctors`\\\", \\\"column\\\": \\\"`name`\\\"}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"99727aab-d62d-59f2-8cea-6bc23db7c0df\",\n        \"name\": \"doctors core details\",\n        \"description\": \"access only to doctors' id and name columns\",\n        \"type\": \"data_access\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"data\": \"[{\\\"table\\\": \\\"`doctors`\\\", \\\"column\\\": \\\"`id`\\\", \\\"schema\\\": \\\"`my-project`.`clinic`\\\"}, {\\\"table\\\": \\\"`doctors`\\\", \\\"column\\\": \\\"`name`\\\", \\\"schema\\\": \\\"`my-project`.`clinic`\\\"}]\",\n        \"createdAt\": \"2026-10-29T23:55:57.023059Z\",\n        \"lastModifiedAt\": \"2026-10-29T23:55:57.023059Z\"\n    }\n}"}],"_postman_id":"bb1dd34c-c1e9-40bc-b5b6-824765410573"},{"name":"Create Row Policy","id":"46726d96-4a25-45b1-85a9-8eb343c0704d","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"female patients only\",\n    \"description\": \"\",\n    \"type\": \"data_filter\",\n    \"data\": \"{\\\"predicate\\\": \\\"`my-project`.`clinic`.`patients`.`gender` = 'F'\\\", \\\"scope\\\": [\\\"`my-project`.`clinic`.`patients`\\\"]}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>Creates a row policy (row-level security). The policy filters the rows a user can see.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The datasource the policy belongs to.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>name</code></strong> (string): The policy name.</li>\n<li><strong><code>description</code></strong> (string): A short description.</li>\n<li><strong><code>type</code></strong> (string): <code>data_filter</code>.</li>\n<li><strong><code>data</code></strong> (string): A JSON string with:<ul>\n<li><strong><code>predicate</code></strong> (string, required): A SQL boolean expression, like a <code>WHERE</code> clause. Write every column with its <strong>full path</strong>: <code>catalog.schema.table.column</code> (or <code>project.dataset.table.column</code> in BigQuery). Use the quote style of your database, for example <code>\"...\"</code> in Postgres and <code>`...`</code> in BigQuery (see <strong>Guide: Writing object names</strong>). You can combine conditions with <code>AND</code>, <code>OR</code>, <code>IN</code>, <code>LIKE</code> and other SQL operators. The <code>\\\"</code> in the API examples is JSON escaping; in the platform, type the predicate as plain SQL.</li>\n<li><strong><code>scope</code></strong> (array of strings, required): The tables the filter applies to, as <code>schema.table</code> full paths. It can not be empty.</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"user-attributes\">User attributes</h4>\n<p>To filter by the user who runs the query, put a user attribute in double curly braces, for example:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>\"bank\".\"core\".\"branches\".\"city\" = {{bank.city}}\n</code></pre><p>PVML replaces <code>{{bank.city}}</code> with the value of the <code>city</code> attribute (in the <code>bank</code> attribute catalog) of the user who runs the query. The attribute must exist in the workspace first.</p>\n<p>📘 For a step-by-step tutorial, see <strong>Guide: Row-level security (RLS)</strong>.</p>\n<h4 id=\"examples\">Examples</h4>\n<ul>\n<li><code>\"main\".\"hr\".\"employees\".\"country\" = 'UK'</code></li>\n<li><code>\"main\".\"hr\".\"employees\".\"country\" IN ('UK', 'IE') AND \"main\".\"hr\".\"employees\".\"is_active\" = true</code></li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A <strong><code>permission</code></strong> object:</p>\n<ul>\n<li><strong><code>id</code></strong> (string): The policy ID.</li>\n<li><strong><code>name</code></strong>, <strong><code>description</code></strong> (string).</li>\n<li><strong><code>type</code></strong> (string): <code>data_filter</code>.</li>\n<li><strong><code>workspaceId</code></strong>, <strong><code>datasourceId</code></strong> (string).</li>\n<li><strong><code>data</code></strong> (string): The policy data as a JSON string. It holds <code>predicate</code>, <code>scope</code> and <code>columns</code> (the columns used in the predicate).</li>\n<li><strong><code>createdAt</code></strong>, <strong><code>lastModifiedAt</code></strong> (string, ISO 8601).</li>\n</ul>\n<p>💡 <code>datasourceName</code> and <code>datasourceType</code> are empty in this response. Use <strong>Get Policies</strong> to see them.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"2991c477-ce21-47e1-b9c9-6a067d6eb0f9","name":"Create Row Policy - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"female patients only\",\n    \"description\": \"\",\n    \"type\": \"data_filter\",\n    \"data\": \"{\\\"predicate\\\": \\\"`my-project`.`clinic`.`patients`.`gender` = 'F'\\\", \\\"scope\\\": [\\\"`my-project`.`clinic`.`patients`\\\"]}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"ac2ffc71-740d-5e6f-b54e-75ad0e3dce3e\",\n        \"name\": \"female patients only\",\n        \"description\": \"\",\n        \"type\": \"data_filter\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"data\": \"{\\\"scope\\\": [\\\"`my-project`.`clinic`.`patients`\\\"], \\\"columns\\\": [\\\"`my-project`.`clinic`.`patients`.`gender`\\\"], \\\"predicate\\\": \\\"`my-project`.`clinic`.`patients`.`gender`='F'\\\"}\",\n        \"createdAt\": \"2026-10-29T23:55:57.562049Z\",\n        \"lastModifiedAt\": \"2026-10-29T23:55:57.562049Z\"\n    }\n}"}],"_postman_id":"46726d96-4a25-45b1-85a9-8eb343c0704d"},{"name":"Create DP Policy","id":"224574ab-16e0-442a-b821-56b1cb5c7dec","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"private clinic schema\",\n    \"description\": \"\",\n    \"type\": \"data_privacy\",\n    \"data\": \"{\\\"schemas\\\": [\\\"`my-project`.`clinic`\\\"]}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>In the platform: <strong>Policies &gt; Privacy &gt; Differential Privacy</strong>.</p>\n<p>Creates a Differential Privacy (DP) policy for one or more schemas. See <strong>Guide: Differential Privacy</strong>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The datasource the policy belongs to.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>name</code></strong> (string): The policy name.</li>\n<li><strong><code>description</code></strong> (string): A short description.</li>\n<li><strong><code>type</code></strong> (string): <code>data_privacy</code>.</li>\n<li><strong><code>data</code></strong> (string): A JSON string with:<ul>\n<li><strong><code>schemas</code></strong> (array of strings): The schemas whose query results get differential privacy.</li>\n</ul>\n</li>\n</ul>\n<p>⚠️ <strong>Before you start:</strong></p>\n<ul>\n<li>The datasource needs a <strong>Standard</strong> snapshot, not Schema-only (API: <code>profile=true</code>, see <strong>Refresh Snapshot</strong>).</li>\n<li>Set an <strong>identifier</strong> for the tables.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A <strong><code>permission</code></strong> object:</p>\n<ul>\n<li><strong><code>id</code></strong> (string): The policy ID.</li>\n<li><strong><code>name</code></strong>, <strong><code>description</code></strong> (string).</li>\n<li><strong><code>type</code></strong> (string): <code>data_privacy</code>.</li>\n<li><strong><code>workspaceId</code></strong>, <strong><code>datasourceId</code></strong> (string).</li>\n<li><strong><code>data</code></strong> (string): The policy data as a JSON string. It holds <code>schemas</code>.</li>\n<li><strong><code>createdAt</code></strong>, <strong><code>lastModifiedAt</code></strong> (string, ISO 8601).</li>\n</ul>\n<p>💡 <code>datasourceName</code> and <code>datasourceType</code> are empty in this response. Use <strong>Get Policies</strong> to see them.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"1ca379a4-0afa-4b32-bb7a-501ee7b8cb1e","name":"Create DP Policy - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"private clinic schema\",\n    \"description\": \"\",\n    \"type\": \"data_privacy\",\n    \"data\": \"{\\\"schemas\\\": [\\\"`my-project`.`clinic`\\\"]}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"dff14d57-617b-5a67-9328-887760b4ec3a\",\n        \"name\": \"private schema\",\n        \"description\": \"\",\n        \"type\": \"data_privacy\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"data\": \"{\\\"schemas\\\": [\\\"`my-project`.`clinic`\\\"]}\",\n        \"createdAt\": \"2026-10-29T23:55:58.124131Z\",\n        \"lastModifiedAt\": \"2026-10-29T23:55:58.124131Z\"\n    }\n}"}],"_postman_id":"224574ab-16e0-442a-b821-56b1cb5c7dec"},{"name":"Create Aggregation Functions Only Policy","id":"1637e5db-43be-4cec-833c-1452ee4f920f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"patients aggregates only\",\n    \"description\": \"\",\n    \"type\": \"data_validation\",\n    \"data\": \"{\\\"header\\\": \\\"aggregation\\\", \\\"aggregationPermission\\\": {\\\"schemas\\\": [], \\\"tables\\\": [\\\"`my-project`.`clinic`.`patients`\\\"]}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>Creates an <strong>Aggregation Functions Only</strong> policy. In the platform: <strong>Policies &gt; Privacy &gt; Aggregation Functions Only</strong>. Users can run only aggregate queries on the chosen schemas or tables, and get exact results.</p>\n<p>💡 In the API the type is <code>data_validation</code>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The datasource the policy belongs to.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>name</code></strong> (string): The policy name.</li>\n<li><strong><code>description</code></strong> (string): A short description.</li>\n<li><strong><code>type</code></strong> (string): <code>data_validation</code>.</li>\n<li><strong><code>data</code></strong> (string): A JSON string with:<ul>\n<li><strong><code>header</code></strong> (string): Always <code>aggregation</code>.</li>\n<li><strong><code>aggregationPermission</code></strong> (object): Where only aggregate queries are allowed:<ul>\n<li><strong><code>schemas</code></strong> (array of strings): Full schema paths. All tables in them are covered.</li>\n<li><strong><code>tables</code></strong> (array of strings): Full table paths (<code>schema.table</code>).</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<p>With this example, users can run <code>SELECT gender, COUNT(*) ... GROUP BY gender</code> on the patients table, but not <code>SELECT *</code>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A <strong><code>permission</code></strong> object:</p>\n<ul>\n<li><strong><code>id</code></strong> (string): The policy ID.</li>\n<li><strong><code>name</code></strong>, <strong><code>description</code></strong> (string).</li>\n<li><strong><code>type</code></strong> (string): <code>data_validation</code>.</li>\n<li><strong><code>workspaceId</code></strong>, <strong><code>datasourceId</code></strong> (string).</li>\n<li><strong><code>data</code></strong> (string): The policy data as a JSON string. It holds <code>header</code> and <code>aggregationPermission</code>.</li>\n<li><strong><code>createdAt</code></strong>, <strong><code>lastModifiedAt</code></strong> (string, ISO 8601).</li>\n</ul>\n<p>💡 <code>datasourceName</code> and <code>datasourceType</code> are empty in this response. Use <strong>Get Policies</strong> to see them.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"fb493749-de5b-40de-9555-aa56d6848097","name":"Create Aggregation Functions Only Policy - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"patients aggregates only\",\n    \"description\": \"\",\n    \"type\": \"data_validation\",\n    \"data\": \"{\\\"header\\\": \\\"aggregation\\\", \\\"aggregationPermission\\\": {\\\"schemas\\\": [], \\\"tables\\\": [\\\"`my-project`.`clinic`.`patients`\\\"]}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"db0b561c-3dba-5816-9621-0e9c1a2742e1\",\n        \"name\": \"patients aggregates only\",\n        \"description\": \"\",\n        \"type\": \"data_validation\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"data\": \"{\\\"header\\\": \\\"aggregation\\\", \\\"aggregationPermission\\\": {\\\"tables\\\": [\\\"`my-project`.`clinic`.`patients`\\\"], \\\"schemas\\\": []}}\",\n        \"createdAt\": \"2026-10-29T23:55:57.749851Z\",\n        \"lastModifiedAt\": \"2026-10-29T23:55:57.749851Z\"\n    }\n}"}],"_postman_id":"1637e5db-43be-4cec-833c-1452ee4f920f"},{"name":"Create Masking Policy","id":"5787fcd6-f511-4154-9f5c-967e34921858","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"mask location details\",\n    \"description\": \"\",\n    \"type\": \"data_masking\",\n    \"data\": \"[{\\\"column\\\": \\\"`my-project`.`clinic`.`locations`.`latitude`\\\", \\\"unmaskLeft\\\": 0, \\\"unmaskRight\\\": 0}, {\\\"column\\\": \\\"`my-project`.`clinic`.`locations`.`longitude`\\\", \\\"unmaskLeft\\\": 0, \\\"unmaskRight\\\": 0}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>This endpoint allows to create a masking policy for a specific datasource.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>datasource_id</code></strong>(string): The unique id of the datasource for this policy.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<ul>\n<li><p><strong><code>name</code></strong> (string)<strong>:</strong> The name of the policy.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string)<strong>:</strong> A brief description or additional details about the policy.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string)<strong>:</strong> The type of the object, which in this case is \"data_masking\".</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): JSON-encoded array containing details of the columns to be masked.</p>\n<ul>\n<li><p><strong><code>column</code></strong> (string): The fully qualified column name that is subject to masking.</p>\n</li>\n<li><p><strong><code>unmaskLeft</code></strong> (number): The number of characters to leave unmasked from the beginning of the value.</p>\n</li>\n<li><p><strong><code>unmaskRight</code></strong> (number): The number of characters to leave unmasked from the end of the value.</p>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p>The response is a <strong><code>permission</code></strong> object in json format that includes the details of the newly created masking policy:</p>\n<ul>\n<li><p><strong><code>id</code></strong> (string): Unique identifier for the policy.</p>\n</li>\n<li><p><strong><code>name</code></strong> (string): Name of the policy.</p>\n</li>\n<li><p><strong><code>description</code></strong> (string): Description or additional details about the policy.</p>\n</li>\n<li><p><strong><code>type</code></strong> (string): Type of the permission, in this case data_masking.</p>\n</li>\n<li><p><strong><code>workspaceId</code></strong> (string): Identifier for the workspace to which the policy belongs.</p>\n</li>\n<li><p><strong><code>data</code></strong> (string): JSON-encoded string containing schemas details.</p>\n<ul>\n<li><p><strong><code>column</code></strong> (string): The fully qualified column name that is subject to masking.</p>\n</li>\n<li><p><strong><code>unmaskLeft</code></strong> (number): The number of characters to leave unmasked from the beginning of the value.</p>\n</li>\n<li><p><strong><code>unmaskRight</code></strong> (number): The number of characters to leave unmasked from the end of the value.</p>\n</li>\n</ul>\n</li>\n<li><p><strong><code>createdAt</code></strong> (timestamp, ISO 8601): The timestamp when the permission was created.</p>\n</li>\n<li><p><strong><code>lastModifiedAt</code></strong> (timestamp, ISO 8601): The timestamp when the permission was last modified.</p>\n</li>\n<li><p><strong><code>vdbId</code></strong> (string): Identifier for the associated VDB, if applicable.</p>\n</li>\n<li><p><strong><code>vdbName</code></strong> (string): Name of the associated VDB, if applicable.</p>\n</li>\n<li><p><strong><code>datasourceId</code></strong> (string): Identifier for the associated datasource.</p>\n</li>\n<li><p><strong><code>datasourceName</code></strong> (string): Name of the associated datasource.</p>\n</li>\n<li><p><strong><code>datasourceType</code></strong> (string): Type of the associated datasource.</p>\n</li>\n</ul>\n<p>📘 PVML also supports <strong>hash</strong> (<code>\"isHash\": true</code>) and <strong>regex</strong> (<code>\"regex\": \"...\"</code>) masking. See <strong>Guide: Masking strategies</strong>.</p>\n<p>💡 Names must be full paths with the right quotes. See <strong>Guide: Writing object names</strong>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"da206d55-fc4b-46fa-b239-e026bc8b99c4","name":"Create Masking Policy - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"mask location details\",\n    \"description\": \"\",\n    \"type\": \"data_masking\",\n    \"data\": \"[{\\\"column\\\": \\\"`my-project`.`clinic`.`locations`.`latitude`\\\", \\\"unmaskLeft\\\": 0, \\\"unmaskRight\\\": 0}, {\\\"column\\\": \\\"`my-project`.`clinic`.`locations`.`longitude`\\\", \\\"unmaskLeft\\\": 0, \\\"unmaskRight\\\": 0}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"558dba71-52d0-5090-aed5-379aae99f635\",\n        \"name\": \"mask location details\",\n        \"description\": \"\",\n        \"type\": \"data_masking\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"data\": \"[{\\\"regex\\\": \\\"\\\", \\\"column\\\": \\\"`my-project`.`clinic`.`locations`.`latitude`\\\", \\\"isHash\\\": false, \\\"unmaskLeft\\\": 0, \\\"unmaskRight\\\": 0}, {\\\"regex\\\": \\\"\\\", \\\"column\\\": \\\"`my-project`.`clinic`.`locations`.`longitude`\\\", \\\"isHash\\\": false, \\\"unmaskLeft\\\": 0, \\\"unmaskRight\\\": 0}]\",\n        \"createdAt\": \"2026-10-29T23:55:57.933032Z\",\n        \"lastModifiedAt\": \"2026-10-29T23:55:57.933032Z\"\n    }\n}"}],"_postman_id":"5787fcd6-f511-4154-9f5c-967e34921858"},{"name":"Create Write Policy","id":"078679ac-347f-416c-8348-af97d0c0e550","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"update account details\",\n    \"description\": \"\",\n    \"type\": \"data_write\",\n    \"data\": \"[{\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"table\\\": \\\"\\\\\\\"accounts\\\\\\\"\\\", \\\"column\\\": \\\"\\\\\\\"income\\\\\\\"\\\"}, {\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"table\\\": \\\"\\\\\\\"accounts\\\\\\\"\\\", \\\"column\\\": \\\"\\\\\\\"is_business\\\\\\\"\\\"}, {\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"table\\\": \\\"\\\\\\\"notes\\\\\\\"\\\", \\\"column\\\": \\\"\\\"}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>Creates a write policy. It allows users of a VDB to change data (<code>INSERT</code>, <code>UPDATE</code>) in specific tables or columns. Without a write policy, a VDB is read-only.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The datasource the policy belongs to.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>name</code></strong>, <strong><code>description</code></strong> (string).</li>\n<li><strong><code>type</code></strong> (string): <code>data_write</code>.</li>\n<li><strong><code>data</code></strong> (string): A JSON array. Each item allows writes to:<ul>\n<li>a <strong>column</strong>: <code>{\"schema\": ..., \"table\": ..., \"column\": \"&lt;column&gt;\"}</code>, or</li>\n<li>a <strong>whole table</strong>: <code>{\"schema\": ..., \"table\": ..., \"column\": \"\"}</code> (empty column).</li>\n</ul>\n</li>\n</ul>\n<p>In this example, users can update <code>income</code> and <code>is_business</code> in <code>accounts</code>, and write any column in <code>notes</code>. Updates of other columns are blocked.</p>\n<p>⚠️ <strong>Before you start:</strong></p>\n<ul>\n<li>The database user that PVML uses for this datasource needs write rights in the database. PVML can only allow what the database allows.</li>\n<li>A write policy does not give read access. Add a column policy for the columns users need to read.</li>\n<li>For a whole table, use an empty <code>column</code> (<code>\"\"</code>).</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A <strong><code>permission</code></strong> object, like the other policy types: <code>id</code>, <code>name</code>, <code>description</code>, <code>type</code> (<code>data_write</code>), <code>workspaceId</code>, <code>datasourceId</code>, <code>data</code> (the JSON array), <code>createdAt</code>, <code>lastModifiedAt</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"a9654f5e-b09f-484a-8586-508dd413fea1","name":"Create Write Policy - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"update account details\",\n    \"description\": \"\",\n    \"type\": \"data_write\",\n    \"data\": \"[{\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"table\\\": \\\"\\\\\\\"accounts\\\\\\\"\\\", \\\"column\\\": \\\"\\\\\\\"income\\\\\\\"\\\"}, {\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"table\\\": \\\"\\\\\\\"accounts\\\\\\\"\\\", \\\"column\\\": \\\"\\\\\\\"is_business\\\\\\\"\\\"}, {\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"table\\\": \\\"\\\\\\\"notes\\\\\\\"\\\", \\\"column\\\": \\\"\\\"}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"687ec3b3-6d2d-56e2-b6ec-24541b954df1\",\n        \"name\": \"update account details\",\n        \"description\": \"\",\n        \"type\": \"data_write\",\n        \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n        \"data\": \"[{\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"table\\\": \\\"\\\\\\\"accounts\\\\\\\"\\\", \\\"column\\\": \\\"\\\\\\\"income\\\\\\\"\\\"}, {\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"table\\\": \\\"\\\\\\\"accounts\\\\\\\"\\\", \\\"column\\\": \\\"\\\\\\\"is_business\\\\\\\"\\\"}, {\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"table\\\": \\\"\\\\\\\"notes\\\\\\\"\\\", \\\"column\\\": \\\"\\\"}]\",\n        \"createdAt\": \"2026-10-30T10:00:00.000000Z\",\n        \"lastModifiedAt\": \"2026-10-30T10:00:00.000000Z\"\n    }\n}"}],"_postman_id":"078679ac-347f-416c-8348-af97d0c0e550"},{"name":"Create Timeout Policy","id":"49ab96dd-8b42-4246-b442-ed83466b33e1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"30 seconds max\",\n    \"description\": \"\",\n    \"type\": \"data_timeout\",\n    \"data\": \"{\\\"duration\\\": 30000000000}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>In the platform: <strong>Policies &gt; Load Control</strong>.</p>\n<p>Creates a timeout policy. PVML stops any query of the VDB that runs longer than the limit. Use it to protect your database from slow or expensive queries.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The datasource the policy belongs to.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>name</code></strong>, <strong><code>description</code></strong> (string).</li>\n<li><strong><code>type</code></strong> (string): <code>data_timeout</code>.</li>\n<li><strong><code>data</code></strong> (string): A JSON object:<ul>\n<li><strong><code>duration</code></strong> (number, required): The maximum run time in <strong>nanoseconds</strong>. For example: 30 seconds = <code>30000000000</code>, 5 minutes = <code>300000000000</code>.</li>\n</ul>\n</li>\n</ul>\n<p>A query that runs longer is stopped.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A <strong><code>permission</code></strong> object, like the other policy types: <code>id</code>, <code>name</code>, <code>description</code>, <code>type</code> (<code>data_timeout</code>), <code>workspaceId</code>, <code>datasourceId</code>, <code>data</code> (<code>duration</code>), <code>createdAt</code>, <code>lastModifiedAt</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"0d31a32c-a6c7-4b8f-a11c-424757ea5915","name":"Create Timeout Policy - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"30 seconds max\",\n    \"description\": \"\",\n    \"type\": \"data_timeout\",\n    \"data\": \"{\\\"duration\\\": 30000000000}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"b823710c-6ecd-512c-bc97-61ee06031cd7\",\n        \"name\": \"30 seconds max\",\n        \"description\": \"\",\n        \"type\": \"data_timeout\",\n        \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n        \"data\": \"{\\\"duration\\\": 30000000000}\",\n        \"createdAt\": \"2026-10-30T10:00:00.000000Z\",\n        \"lastModifiedAt\": \"2026-10-30T10:00:00.000000Z\"\n    }\n}"}],"_postman_id":"49ab96dd-8b42-4246-b442-ed83466b33e1"},{"name":"Create Row Limit Policy","id":"77813283-7cea-4146-9751-03518de96cde","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"max 1000 rows\",\n    \"description\": \"\",\n    \"type\": \"data_limit\",\n    \"data\": \"{\\\"maxRows\\\": 1000}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>In the platform: <strong>Policies &gt; Load Control</strong>.</p>\n<p>Creates a row limit policy. A query of the VDB returns at most this number of rows. Use it to stop large data exports.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The datasource the policy belongs to.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>name</code></strong>, <strong><code>description</code></strong> (string).</li>\n<li><strong><code>type</code></strong> (string): <code>data_limit</code>.</li>\n<li><strong><code>data</code></strong> (string): A JSON object:<ul>\n<li><strong><code>maxRows</code></strong> (number, required): The maximum number of rows in a result.</li>\n</ul>\n</li>\n</ul>\n<p>💡 The query does not fail. PVML returns only the first rows, up to the limit. For example, with <code>maxRows</code> 100, <code>SELECT * FROM accounts</code> returns 100 rows even if the table has 5,000.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A <strong><code>permission</code></strong> object, like the other policy types: <code>id</code>, <code>name</code>, <code>description</code>, <code>type</code> (<code>data_limit</code>), <code>workspaceId</code>, <code>datasourceId</code>, <code>data</code> (<code>maxRows</code>), <code>createdAt</code>, <code>lastModifiedAt</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"27196c55-c2c0-4a65-b2ea-aa9e5b456872","name":"Create Row Limit Policy - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"max 1000 rows\",\n    \"description\": \"\",\n    \"type\": \"data_limit\",\n    \"data\": \"{\\\"maxRows\\\": 1000}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"d4edc7de-9350-51e0-a91c-327813a31e68\",\n        \"name\": \"max 1000 rows\",\n        \"description\": \"\",\n        \"type\": \"data_limit\",\n        \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n        \"data\": \"{\\\"maxRows\\\": 1000}\",\n        \"createdAt\": \"2026-10-30T10:00:00.000000Z\",\n        \"lastModifiedAt\": \"2026-10-30T10:00:00.000000Z\"\n    }\n}"}],"_postman_id":"77813283-7cea-4146-9751-03518de96cde"},{"name":"Create Routines Policy","id":"18bdf9b1-cf91-4d36-82b6-c95ead30a870","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"month-end routines\",\n    \"description\": \"\",\n    \"type\": \"data_routines\",\n    \"data\": \"[{\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"itemName\\\": \\\"\\\\\\\"close_month\\\\\\\"\\\", \\\"itemType\\\": \\\"procedure\\\"}, {\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"itemName\\\": \\\"\\\\\\\"is_weekend\\\\\\\"\\\", \\\"itemType\\\": \\\"function\\\"}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>Creates a routines policy. It lists the stored procedures and user-defined functions that users of a VDB may run. Without a routines policy, a VDB can not run any routine.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The datasource the policy belongs to.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>name</code></strong>, <strong><code>description</code></strong> (string).</li>\n<li><strong><code>type</code></strong> (string): <code>data_routines</code>.</li>\n<li><strong><code>data</code></strong> (string): A JSON array. Each item has:<ul>\n<li><strong><code>schema</code></strong> (string): The full schema name.</li>\n<li><strong><code>itemName</code></strong> (string): The routine name, quoted like a table name.</li>\n<li><strong><code>itemType</code></strong> (string): <code>procedure</code> or <code>function</code>.</li>\n</ul>\n</li>\n</ul>\n<p>In this example, users can run the <code>close_month</code> procedure and the <code>is_weekend</code> function. Any other routine is blocked.</p>\n<p>💡 Copy the routine names from <strong>Get Snapshot</strong> (the <code>procedures</code> and <code>functions</code> of each schema).</p>\n<p>⚠️ The routine must exist in the datasource snapshot. Refresh the snapshot after you add new routines to the database.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A <strong><code>permission</code></strong> object, like the other policy types: <code>id</code>, <code>name</code>, <code>description</code>, <code>type</code> (<code>data_routines</code>), <code>workspaceId</code>, <code>datasourceId</code>, <code>data</code> (the JSON array), <code>createdAt</code>, <code>lastModifiedAt</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"f2ee404a-4774-4de1-a8cd-95500bd90b10","name":"Create Routines Policy - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"month-end routines\",\n    \"description\": \"\",\n    \"type\": \"data_routines\",\n    \"data\": \"[{\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"itemName\\\": \\\"\\\\\\\"close_month\\\\\\\"\\\", \\\"itemType\\\": \\\"procedure\\\"}, {\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"itemName\\\": \\\"\\\\\\\"is_weekend\\\\\\\"\\\", \\\"itemType\\\": \\\"function\\\"}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"0f6efbbb-64c2-572d-a7d1-bd7750fce1a1\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"1043e80f-d22e-5b8a-b3bf-02c865d7d18b\",\n        \"name\": \"month-end routines\",\n        \"description\": \"\",\n        \"type\": \"data_routines\",\n        \"workspaceId\": \"b70ad5a7-089b-5a0f-a370-f34719a9621f\",\n        \"data\": \"[{\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"itemName\\\": \\\"\\\\\\\"close_month\\\\\\\"\\\", \\\"itemType\\\": \\\"procedure\\\"}, {\\\"schema\\\": \\\"\\\\\\\"bank\\\\\\\".\\\\\\\"core\\\\\\\"\\\", \\\"itemName\\\": \\\"\\\\\\\"is_weekend\\\\\\\"\\\", \\\"itemType\\\": \\\"function\\\"}]\",\n        \"createdAt\": \"2026-10-30T10:00:00.000000Z\",\n        \"lastModifiedAt\": \"2026-10-30T10:00:00.000000Z\"\n    }\n}"}],"_postman_id":"18bdf9b1-cf91-4d36-82b6-c95ead30a870"},{"name":"Get Datasource Policies","id":"5ef490a6-ac92-4632-a378-3d5b1edfc187","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions","description":"<p>Lists all policies of one datasource, with their data.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>permissions</code></strong> (array): Policy objects (see <strong>Create Column Policy</strong>).</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"a5560fd4-02fa-40c2-84b6-0f7aa3f9716f","name":"Get Datasource Policies - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permissions\": [\n        {\n            \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n            \"datasourceName\": \"MyDB\",\n            \"datasourceType\": \"BigQuery\",\n            \"id\": \"a8411a99-6e3c-55f5-b22b-e46eda32aeb1\",\n            \"name\": \"core read access\",\n            \"description\": \"\",\n            \"type\": \"data_access\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"data\": \"[{\\\"table\\\": \\\"*\\\", \\\"column\\\": \\\"*\\\", \\\"schema\\\": \\\"`my-project`.`core`\\\"}]\",\n            \"createdAt\": \"2026-10-30T01:05:48.066941Z\",\n            \"lastModifiedAt\": \"2026-10-30T01:05:48.066941Z\"\n        }\n    ]\n}"}],"_postman_id":"5ef490a6-ac92-4632-a378-3d5b1edfc187"},{"name":"Get Policy","id":"98bdc609-2f63-43d4-af95-5d147e3db711","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions/{{policy_id}}","description":"<p>Returns one policy with its data.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n<li><strong><code>policy_id</code></strong> (string): The policy ID.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions","{{policy_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"29a95ee8-6254-4102-a52f-7c0e7b6bfbe5","name":"Get Policy - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions/{{policy_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceName\": \"MyDB\",\n        \"datasourceType\": \"BigQuery\",\n        \"id\": \"a8411a99-6e3c-55f5-b22b-e46eda32aeb1\",\n        \"name\": \"core read access\",\n        \"description\": \"\",\n        \"type\": \"data_access\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"data\": \"[{\\\"table\\\": \\\"*\\\", \\\"column\\\": \\\"*\\\", \\\"schema\\\": \\\"`my-project`.`core`\\\"}]\",\n        \"createdAt\": \"2026-10-30T01:05:48.066941Z\",\n        \"lastModifiedAt\": \"2026-10-30T01:05:48.066941Z\"\n    }\n}"}],"_postman_id":"98bdc609-2f63-43d4-af95-5d147e3db711"},{"name":"Update Policy","id":"63b3f247-2445-4d6d-ae19-4cc1edb8e6a1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"core read access\",\n    \"description\": \"read access to the core schema\",\n    \"type\": \"data_access\",\n    \"data\": \"[{\\\"schema\\\": \\\"`my-project`.`core`\\\", \\\"table\\\": \\\"\\\", \\\"column\\\": \\\"\\\"}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions/{{policy_id}}","description":"<p>Changes a policy. All VDBs that use it get the change right away.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n<li><strong><code>policy_id</code></strong> (string): The policy ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>The full policy, the same fields as when you create it: <code>name</code>, <code>description</code>, <code>type</code>, <code>data</code>. Fields you leave out are not kept.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 201 with the updated <strong><code>permission</code></strong> object.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions","{{policy_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"d21fc4bc-1ec5-47ad-86f0-2f0fe5d6e455","name":"Update Policy - 201 Created","originalRequest":{"method":"PUT","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"core read access\",\n    \"description\": \"read access to the core schema\",\n    \"type\": \"data_access\",\n    \"data\": \"[{\\\"schema\\\": \\\"`my-project`.`core`\\\", \\\"table\\\": \\\"\\\", \\\"column\\\": \\\"\\\"}]\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions/{{policy_id}}"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"a8411a99-6e3c-55f5-b22b-e46eda32aeb1\",\n        \"name\": \"core read access\",\n        \"description\": \"read access to the core schema\",\n        \"type\": \"data_access\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"data\": \"[{\\\"table\\\": \\\"*\\\", \\\"column\\\": \\\"*\\\", \\\"schema\\\": \\\"`my-project`.`core`\\\"}]\",\n        \"createdAt\": \"2026-10-30T01:05:48.066941Z\",\n        \"lastModifiedAt\": \"2026-10-30T01:05:48.635997Z\"\n    }\n}"}],"_postman_id":"63b3f247-2445-4d6d-ae19-4cc1edb8e6a1"},{"name":"Duplicate Policy","id":"fee08993-46de-4794-a735-c44ae1ff9f2f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions/{{policy_id}}/duplicate","description":"<p>Creates a copy of a policy, named <code>&lt;name&gt; - copy</code>. The copy is not attached to any VDB. Use it as a starting point for a similar policy.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The unique identifier of the datasource.</li>\n<li><strong><code>policy_id</code></strong> (string): The policy to copy.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The new <strong><code>permission</code></strong> object.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions","{{policy_id}}","duplicate"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"182565a7-4232-49e9-abb2-0657a45ce595","name":"Duplicate Policy - 200 OK","originalRequest":{"method":"POST","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions/{{policy_id}}/duplicate"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"permission\": {\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceName\": \"\",\n        \"datasourceType\": \"\",\n        \"id\": \"b14c70e5-cf94-5bdf-af24-893642916659\",\n        \"name\": \"core read access - copy\",\n        \"description\": \"read access to the core schema\",\n        \"type\": \"data_access\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"data\": \"[{\\\"table\\\": \\\"*\\\", \\\"column\\\": \\\"*\\\", \\\"schema\\\": \\\"`my-project`.`core`\\\"}]\",\n        \"createdAt\": \"2026-10-30T01:05:48.822454Z\",\n        \"lastModifiedAt\": \"2026-10-30T01:05:48.822454Z\"\n    }\n}"}],"_postman_id":"fee08993-46de-4794-a735-c44ae1ff9f2f"},{"name":"Delete Policy","id":"e1a756f7-e7e2-4d9d-99b2-288fb7643ef1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions/{{policy_id}}","description":"<p>Deletes a policy. It is removed from every VDB that uses it.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>datasource_id</code></strong> (string): The datasource of the policy.</li>\n<li><strong><code>policy_id</code></strong> (string): The policy ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>💡 Check <strong>Get Virtual DB Policies</strong> first: users of those VDBs lose what the policy gave them.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","datasources","{{datasource_id}}","permissions","{{policy_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"b91d2e38-38ff-4ddf-aa86-cea83be4e63e","name":"Delete Policy - 200 OK","originalRequest":{"method":"DELETE","header":[],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/datasources/{{datasource_id}}/permissions/{{policy_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"e1a756f7-e7e2-4d9d-99b2-288fb7643ef1"}],"id":"19b40658-590d-463a-baa2-168c5fbc9d3c","description":"<p>PVML provides full granularity in access policies to support any level of security and privacy according to the specific requirements of the business use case. Policies can be attached to a <strong>VDB</strong> to create a certain scope for a datasource.</p>\n<p>The platform groups policies in these tabs. The API name of each type is in brackets.</p>\n<ol>\n<li><strong>Column</strong>: read access to schemas, tables and columns (<code>data_access</code>)</li>\n<li><strong>Row</strong>: filters rows (<code>data_filter</code>)</li>\n<li><strong>Masking</strong>: hides values (<code>data_masking</code>)</li>\n<li><strong>Privacy</strong>: two kinds:<ul>\n<li><strong>Differential Privacy</strong> (<code>data_privacy</code>)</li>\n<li><strong>Aggregation Functions Only</strong> (<code>data_validation</code>)</li>\n</ul>\n</li>\n<li><strong>Load Control</strong>: two kinds:<ul>\n<li><strong>Timeout</strong> (<code>data_timeout</code>)</li>\n<li><strong>Row limit</strong> (<code>data_limit</code>)</li>\n</ul>\n</li>\n<li><strong>Write</strong>: <code>INSERT</code> and <code>UPDATE</code> (<code>data_write</code>)</li>\n<li><strong>Routines</strong>: stored procedures and functions (<code>data_routines</code>)</li>\n</ol>\n<p>📘 <strong>Guides in this section:</strong> Row-level security (RLS), How policies work together, Masking strategies, Differential Privacy, Writing object names.</p>\n<h3 id=\"column-level-policy\">Column Level Policy</h3>\n<ul>\n<li><p>Specifies the parts of a data source that are accessible, including schemas, tables, and individual columns.</p>\n</li>\n<li><p>A column-level policy must be added to a VDB for it to be valid. Ensure this is set up.</p>\n</li>\n</ul>\n<h3 id=\"row-level-policy\">Row Level Policy</h3>\n<ul>\n<li>Filters the rows a user can see, with a SQL condition like <code>\"main\".\"crm\".\"customers\".\"city\" = 'London'</code>.</li>\n<li>You can combine conditions with <code>AND</code>, <code>OR</code>, <code>IN</code> and other SQL operators.</li>\n<li>You can use user attributes, for example <code>\"bank\".\"core\".\"branches\".\"city\" = {{bank.city}}</code>. Each user sees only the rows that match their own attributes.</li>\n<li>See <strong>Guide: Row-level security (RLS)</strong> for a full tutorial.</li>\n</ul>\n<h3 id=\"privacy-differential-privacy\">Privacy: Differential Privacy</h3>\n<h4 id=\"💡-what-is-differential-privacy\"><strong>💡 What is Differential Privacy?</strong></h4>\n<ul>\n<li><p><a href=\"https://pvml.com/our-technology/#differential-privacy\">Differential Privacy</a> is a mathematical framework that offers the strongest data safeguard in data-driven systems.</p>\n</li>\n<li><p>It achieves this by <strong>adding controlled noise to the output of a query or algorithm</strong>, making it <strong>statistically indistinguishable whether a particular individual’s data was included or excluded</strong>.</p>\n</li>\n<li><p>This nominal amount of variance is insignificant in a broader analytical context, but is crucial to create a robust barrier against attempts to extract sensitive information.</p>\n</li>\n</ul>\n<img src=\"https://pvml.com/wp-content/uploads/2023/12/DP-algorithm-v2-1.svg\" alt=\"A%20DP%20algorithm%20makes%20it%20statistically%20indistinguishable%20whether%20a%20particular%20individual’s%20data%20was%20included%20or%20excluded%20in%20the%20computation%20(so%20whether%20there%20were%2010K%20records%20or%209,999%20records).\" width=\"616\" height=\"685\" />\n\n<ul>\n<li><p>A Differential Privacy policy allows to choose a <strong>schema</strong> for which outputs will be differentially private.</p>\n</li>\n<li><p>With a DP policy in place, the user can extract insights about populations (groups of <strong>identifiers</strong> as defined when connecting the datasource) but cannot access raw data for individual identifiers or very small samples.</p>\n</li>\n<li><p>This mechanism is based on mathematical Differential Privacy algorithms that are applied in real time and require no additional configurations or alterations to the underlying data.</p>\n</li>\n</ul>\n<h4 id=\"💡-when-should-i-use-a-differential-privacy-policy\"><strong>💡 When should I use a Differential Privacy policy?</strong></h4>\n<ul>\n<li><p>When you need to protect the privacy of individuals in the data (e.g., users, transactions, etc.).</p>\n</li>\n<li><p>When your focus is on statistical analysis rather than retrieving or analyzing values related to individual records.</p>\n</li>\n<li><p>When working with large datasets (roughly &gt;10K records) rather than small datasets.</p>\n</li>\n</ul>\n<p>⚠️ <strong>Note:</strong></p>\n<ul>\n<li><p>To use a Differential Privacy policy, the datasource needs a <strong>Standard</strong> snapshot, and an <strong>Identifier</strong> must be configured for the datasource.</p>\n</li>\n<li><p>Differential Privacy algorithms can be highly complex, thus adding some overhead in query execution times.</p>\n</li>\n</ul>\n<p>📘 See <strong>Guide: Differential Privacy</strong> for examples, what you can run and how to set it up.</p>\n<h3 id=\"masking-policy\">Masking Policy</h3>\n<ul>\n<li>Hides the values of chosen columns in query results, fully or partly. The data in your database does not change.</li>\n<li>With hash masking, users can still join and count on a masked column without seeing its values.</li>\n<li>See <strong>Guide: Masking strategies</strong>.</li>\n</ul>\n<h3 id=\"privacy-aggregation-functions-only\">Privacy: Aggregation Functions Only</h3>\n<ul>\n<li>Users can run only aggregate queries (<code>COUNT</code>, <code>SUM</code>, <code>AVG</code> with <code>GROUP BY</code> and so on) on the chosen schemas or tables. <code>SELECT *</code> and other row-level queries are blocked.</li>\n<li>Results are exact, with no noise. Use it when totals are fine to share, but single rows are not.</li>\n<li>In the API the type is <code>data_validation</code>. See <strong>Create Aggregation Functions Only Policy</strong>.</li>\n</ul>\n<h3 id=\"write-policy\">Write Policy</h3>\n<ul>\n<li>Allows <code>INSERT</code> and <code>UPDATE</code> in chosen tables or columns. Without it, a VDB is read-only.</li>\n</ul>\n<h3 id=\"load-control-timeout-and-row-limit\">Load Control: Timeout and Row Limit</h3>\n<ul>\n<li><strong>Timeout</strong> stops queries that run too long.</li>\n<li><strong>Row limit</strong> caps the number of rows a query returns.</li>\n<li>Together they protect your database and your cloud bill from heavy queries.</li>\n</ul>\n<h3 id=\"routines-policy\">Routines Policy</h3>\n<ul>\n<li>An allow list of stored procedures and functions that users may run.</li>\n</ul>\n","_postman_id":"19b40658-590d-463a-baa2-168c5fbc9d3c","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Connections","item":[{"name":"REST API","item":[{"name":"Create User API Key","id":"227af741-9263-4a29-931d-a3ea5a91d8a1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"reporting script\",\n    \"description\": \"used by the nightly report\",\n    \"expiration\": 1766750237000\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/token","description":"<p>Creates a REST API key for a user (a person or a service account). In the platform: <strong>Connections &gt; REST API</strong>. Send the key as <code>Authorization: Bearer &lt;key&gt;</code>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><code>workspace_id</code>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><code>user_id</code> (string): The unique identifier of the user in the workspace.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<ul>\n<li><p><code>name</code> (string): A name for the key.</p>\n</li>\n<li><p><code>description</code> (string)<strong>:</strong> A string that describes the token's purpose.</p>\n</li>\n<li><p><code>expiration</code> (string): The token expiration time represented in Unix epoch time, measured in milliseconds.</p>\n</li>\n</ul>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<ul>\n<li><p><code>token</code> (string)<strong>:</strong> The API key. You see it only once.</p>\n</li>\n<li><p><code>tokenId</code> (string): A unique identifier for the token instance, used for tracking or revocation.</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","users","{{user_id}}","token"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"d2d3221d-e49d-42cf-a414-309d02b8b7ba","name":"Create User API Key - 201 Created","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"reporting script\",\n    \"description\": \"used by the nightly report\",\n    \"expiration\": 1766750237000\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/users/{{user_id}}/token"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"token\": \"{{json_web_token_105i}}\",\n    \"tokenId\": \"00000000-0000-0000-0000-000000000000\"\n}"}],"_postman_id":"227af741-9263-4a29-931d-a3ea5a91d8a1"},{"name":"Get API Keys","id":"ab758f95-514b-4c4d-8e78-2277afa9c4a1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/token","description":"<p>Lists the API keys of the workspace. The response never includes the secret key itself: PVML shows it only once, when you create it.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>tokensMetadata</code></strong> (array). Each item has <code>Id</code> (use it to revoke), <code>Name</code>, <code>Description</code>, <code>TargetUserEmail</code> (the user the key acts as), <code>CreatorUserEmail</code>, <code>CreatedAt</code>, <code>ExpirationDate</code> (Unix time in ms), <code>IsRevoked</code>.</li>\n</ul>\n<p>💡 Revoked keys are listed too, so you keep a history.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","token"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"b3634ab1-7717-47c9-b553-dd08aa25f1c6","name":"Get API Keys - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/token"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"tokensMetadata\": [\n        {\n            \"Id\": \"37d32dd9-7412-5583-a6a5-fa563e9cd375\",\n            \"TargetUserEmail\": \"jane.doe@example.com\",\n            \"CreatorUserEmail\": \"jane.doe@example.com\",\n            \"CreatedAt\": 1793318550841,\n            \"Name\": \"reporting script\",\n            \"Description\": \"used by the nightly report\",\n            \"ExpirationDate\": 1793322150596,\n            \"IsRevoked\": true\n        }\n    ]\n}"}],"_postman_id":"ab758f95-514b-4c4d-8e78-2277afa9c4a1"},{"name":"Revoke API Key","id":"b94622e5-efd7-40da-ba14-13c8cf45a59e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"tokenId\": \"{{token_id}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/token/revoke","description":"<p>This endpoint allows to revoke an API token.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><code>workspace_id</code>(string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<ul>\n<li><code>tokenId</code> (string)<strong>:</strong> A unique identifier of the token to revoke.</li>\n</ul>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p>Upon successfully revoking the token, the server returns a status code of 200 and an empty JSON response.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","token","revoke"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"255dce71-d4cc-4bb0-9b89-92d7d5088ecc","name":"Revoke API Key - 200 Success","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"tokenId\": \"{{token_id}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/token/revoke"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"\"\""}],"_postman_id":"b94622e5-efd7-40da-ba14-13c8cf45a59e"}],"id":"e71a9430-590a-4251-8588-c307e311e45b","description":"<p>API keys for this REST API. In the platform: <strong>Connections &gt; REST API</strong>.</p>\n<p>See the <strong>Connections</strong> folder for when to use which.</p>\n","_postman_id":"e71a9430-590a-4251-8588-c307e311e45b","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"ODBC / JDBC","item":[{"name":"Create ODBC/JDBC Connection String","id":"98e5363a-e669-420f-a902-5dbd35e93204","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"tableau\",\n    \"description\": \"connection for the BI team\",\n    \"expiration\": 1798761600000\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/users/{{user_id}}/connection_strings","description":"<p>Creates an ODBC/JDBC connection string for one user and one VDB. A BI tool, SQL editor or app uses it to run SQL on the VDB, as that user. In the platform: <strong>Connections &gt; ODBC / JDBC &gt; Create New</strong>.</p>\n<p>See the <strong>Connections</strong> folder for the full flow and an example.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>vdb_id</code></strong> (string): The unique identifier of the VDB.</li>\n<li><strong><code>user_id</code></strong> (string): The user (person or service account) the tool acts as. The user must have access to the VDB.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>name</code></strong> (string): A name for the connection string.</li>\n<li><strong><code>description</code></strong> (string): A short description.</li>\n<li><strong><code>expiration</code></strong> (number, required): Expiry time, Unix time in <strong>milliseconds</strong>. Must be in the future.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>A <strong><code>DataSourceConnectionInfo</code></strong> object. Its fields depend on the type of the datasource, because PVML looks like that datasource to the client. For example:</p>\n<ul>\n<li>BigQuery: <code>host</code> (a BigQuery API endpoint, for example <code>bigquery-proxy.pvml.com:443/bigquery/v2/</code>, without <code>https://</code>) and <code>credentialFile</code> (the JSON key file, as text). Use them with your BigQuery driver or client library as a custom API endpoint and a service account key.</li>\n<li>Databricks: host, HTTP path and access token.</li>\n<li>Other types: a URL (JDBC URL or connection URL) and, for some types, a token.</li>\n</ul>\n<p>⚠️ The response holds secret credentials. Keep it safe. You can see it only once.</p>\n<p>To list or revoke connection strings, see <strong>Get ODBC/JDBC Connection Strings</strong> and <strong>Revoke ODBC/JDBC Connection String</strong>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}","users","{{user_id}}","connection_strings"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"7d3de731-d725-4dc6-a564-b52c5e79905c","name":"Create ODBC/JDBC Connection String - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"tableau\",\n    \"description\": \"connection for the BI team\",\n    \"expiration\": 1798761600000\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}/users/{{user_id}}/connection_strings"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"DataSourceConnectionInfo\": {\n        \"host\": \"bigquery-proxy.pvml.com:443/bigquery/v2/\",\n        \"credentialFile\": \"{\\\"type\\\": \\\"service_account\\\", \\\"project_id\\\": \\\"my-project\\\", \\\"private_key\\\": \\\"<private key>\\\", \\\"client_email\\\": \\\"...\\\"}\"\n    }\n}"}],"_postman_id":"98e5363a-e669-420f-a902-5dbd35e93204"},{"name":"Get ODBC/JDBC Connection Strings","id":"60c7de05-0fc7-404f-a07b-b2681639a5d1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/connection_strings","description":"<p>Lists the ODBC/JDBC connection strings in a workspace, like the <strong>Connections &gt; ODBC / JDBC</strong> page in the platform. The response has no secrets.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>connectionStringsMetadata</code></strong> (array). Each item has:<ul>\n<li><strong><code>id</code></strong> (string): The connection string ID. Use it to revoke.</li>\n<li><strong><code>name</code></strong>, <strong><code>description</code></strong> (string).</li>\n<li><strong><code>vdbId</code></strong> (string), <strong><code>vdbType</code></strong> (string): The VDB it connects to.</li>\n<li><strong><code>targetUserEmail</code></strong> (string): The user who uses the connection.</li>\n<li><strong><code>creatorUserEmail</code></strong> (string): The user who created it.</li>\n<li><strong><code>createdAt</code></strong>, <strong><code>expirationDateTime</code></strong> (number): Unix time in milliseconds.</li>\n<li><strong><code>isRevoked</code></strong> (boolean).</li>\n</ul>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","connection_strings"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"6f200e53-795f-4b80-8ede-3a778c6b53fc","name":"Get ODBC/JDBC Connection Strings - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/connection_strings"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"connectionStringsMetadata\": [\n        {\n            \"id\": \"04b1bb4d-18a9-5a40-9682-97f53543b5b6\",\n            \"name\": \"tableau\",\n            \"description\": \"connection for Tableau\",\n            \"vdbId\": \"df8fe2c3-7c75-5f3e-8d47-d08181298485\",\n            \"vdbType\": \"vdb\",\n            \"targetUserEmail\": \"jane.doe@example.com\",\n            \"creatorUserEmail\": \"jane.doe@example.com\",\n            \"createdAt\": 1793318163160,\n            \"expirationDateTime\": 1793321756015,\n            \"isRevoked\": false\n        }\n    ]\n}"}],"_postman_id":"60c7de05-0fc7-404f-a07b-b2681639a5d1"},{"name":"Revoke ODBC/JDBC Connection String","id":"a9e468d2-3f40-4c0e-bf5b-cd58602d21da","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"connectionStringId\": \"{{connection_string_id}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/connection_strings/revoke","description":"<p>Revokes a connection string. The client can no longer connect with it.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>connectionStringId</code></strong> (string): The ID from <strong>Get ODBC/JDBC Connection Strings</strong>.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","connection_strings","revoke"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"c9d18856-0e15-4045-9056-4aace3b50544","name":"Revoke ODBC/JDBC Connection String - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"connectionStringId\": \"{{connection_string_id}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/connection_strings/revoke"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"a9e468d2-3f40-4c0e-bf5b-cd58602d21da"}],"id":"2569d2a9-3775-4fec-bc21-25476e05c3cf","description":"<p>Connection strings for BI tools, SQL editors and apps. In the platform: <strong>Connections &gt; ODBC / JDBC</strong>.</p>\n<p>See the <strong>Connections</strong> folder for the full flow.</p>\n","_postman_id":"2569d2a9-3775-4fec-bc21-25476e05c3cf","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}}],"id":"f47d772e-7c61-473b-bf8d-28d5227a417c","description":"<p>Connections let tools outside the PVML platform reach PVML. The platform has two kinds, on two tabs of the <strong>Connections</strong> page:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Platform tab</th>\n<th>What it is</th>\n<th>Use it for</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>REST API</strong></td>\n<td>An API key</td>\n<td>Calling this API: scripts, apps, SCIM, the admin MCP server</td>\n</tr>\n<tr>\n<td><strong>ODBC / JDBC</strong></td>\n<td>A connection string for <strong>one VDB</strong></td>\n<td>Running SQL on a VDB from a BI tool (Tableau, Power BI), a SQL editor or an app</td>\n</tr>\n</tbody>\n</table>\n</div><p>Both are created <strong>for a user</strong> (a person or a service account) and both have an <strong>expiry date</strong>. Whatever the tool does, it does <strong>as that user</strong>: same role, same VDB policies, and every query is in the audit log.</p>\n<h3 id=\"rest-api-keys\">REST API keys</h3>\n<ul>\n<li>Create one with <strong>Create User API Key</strong>. Send it in every request as <code>Authorization: Bearer &lt;key&gt;</code>.</li>\n<li>The key has the role of its user. A key of a member can not do admin actions.</li>\n<li>You see the key only once. List keys with <strong>Get API Keys</strong> (no secrets in the list) and revoke them with <strong>Revoke API Key</strong>.</li>\n</ul>\n<h3 id=\"odbc--jdbc-connection-strings\">ODBC / JDBC connection strings</h3>\n<p>A connection string connects a tool to <strong>one VDB</strong>. To the tool, PVML looks like the database behind the VDB. So you use the <strong>normal driver of that database</strong>: a VDB on BigQuery is used with the BigQuery driver, a VDB on Databricks with the Databricks driver, and so on. The tool needs no PVML plugin.</p>\n<p><strong>Example: the BI team wants Tableau on the \"London branch\" VDB.</strong></p>\n<ol>\n<li>Create a service account for the dashboard, for example <code>tableau-london</code> (<strong>Users &gt; Create Service Account</strong>).</li>\n<li>Give it access to the VDB (<strong>Virtual DBs &gt; Update Virtual DB Users</strong>). The user must have access to the VDB.</li>\n<li>Create the connection string (<strong>Create ODBC/JDBC Connection String</strong>) with a name, a description and an expiry date.</li>\n<li>Copy the fields from the response into Tableau. They depend on the datasource type, for example:<ul>\n<li><strong>BigQuery:</strong> a <strong>Host</strong> and a <strong>JSON key file</strong> (in the platform: <strong>Download JSON-key file</strong>).</li>\n<li><strong>Databricks:</strong> a <strong>Host</strong>, an <strong>HTTP Path</strong> and an <strong>Access Token</strong>.</li>\n<li><strong>Other types:</strong> a <strong>JDBC URL</strong> or <strong>Connection URL</strong>, and for some types a <strong>Token</strong>.</li>\n</ul>\n</li>\n<li>Tableau now sees only what the VDB allows: the London rows, with masked names. Every dashboard refresh is in the audit log.</li>\n<li>When the dashboard is retired, revoke the connection (<strong>Revoke ODBC/JDBC Connection String</strong>). It also stops working by itself on the expiry date.</li>\n</ol>\n<p>⚠️ You see the secret fields only once, in the response. Store them in your tool or secret manager right away.</p>\n<p>💡 <strong>Why one connection per tool?</strong> If one tool is lost or no longer needed, you revoke only its connection. The other tools keep working, and the audit log shows which tool ran which query.</p>\n","_postman_id":"f47d772e-7c61-473b-bf8d-28d5227a417c","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"GenAI","item":[{"name":"MCP","item":[{"name":"Get MCPs","id":"bb6b312e-c881-47cb-bc27-87c621b1e35d","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps","description":"<p>Lists the MCP servers of the workspace (one per VDB).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"query-parameters-optional\">Query Parameters (optional)</h3>\n<ul>\n<li><strong><code>pageNumber</code></strong>, <strong><code>pageSize</code></strong> (max 500): For pages.</li>\n<li><strong><code>searchText</code></strong> (string): Filter by name.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>mcps</code></strong> (array). Each item: <code>id</code>, <code>name</code> (the VDB name), <code>description</code>, <code>type</code> (<code>virtual DB</code>), <code>url</code> (the MCP URL), <code>internalReferences.vdbId</code>, <code>createdAt</code>.</li>\n<li><strong><code>pagination</code></strong> (object), when you ask for pages.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","mcps"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"e57a6f1e-11bd-497c-a3cd-a0ccd21f0f6e","name":"Get MCPs - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"mcps\": [\n        {\n            \"id\": \"820201bf-8c74-5946-9776-f7653d44c124\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"name\": \"branch managers\",\n            \"description\": \"\",\n            \"type\": \"virtual DB\",\n            \"url\": \"https://platform.pvml.com/platform/api/v1/mcp/workspaces/5a080071-4995-52ef-8dce-9b5ad367a3d4/vdbs/607187ab-3f63-5c73-9ce8-779969cce2d1\",\n            \"createdBy\": \"PVML integration\",\n            \"createdAt\": \"2026-10-30T01:05:49.037363Z\",\n            \"internalReferences\": {\n                \"vdbId\": \"607187ab-3f63-5c73-9ce8-779969cce2d1\",\n                \"vdbImage\": {\n                    \"image\": \"\"\n                }\n            },\n            \"image\": \"\"\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 2,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 2,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"bb6b312e-c881-47cb-bc27-87c621b1e35d"},{"name":"Get Permitted MCPs","id":"f5012f15-0fcc-4bb4-8a7f-839e990661c4","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps/permitted","description":"<p>Lists the MCP servers that the current user may use. Use it in your app to show each user the AI data sources they can connect to.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>mcps</code></strong> (array): The same format as <strong>Get MCPs</strong>. <code>null</code> when there are none.</li>\n</ul>\n<p>Any workspace member can call it.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","mcps","permitted"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"606e819c-728f-40d9-a9e5-685424556322","name":"Get Permitted MCPs - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps/permitted"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"mcps\": [\n        {\n            \"id\": \"820201bf-8c74-5946-9776-f7653d44c124\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"name\": \"branch managers\",\n            \"description\": \"\",\n            \"type\": \"virtual DB\",\n            \"url\": \"https://platform.pvml.com/platform/api/v1/mcp/workspaces/5a080071-4995-52ef-8dce-9b5ad367a3d4/vdbs/607187ab-3f63-5c73-9ce8-779969cce2d1\",\n            \"createdBy\": \"PVML integration\",\n            \"createdAt\": \"2026-10-30T01:05:49.037363Z\",\n            \"internalReferences\": {\n                \"vdbId\": \"607187ab-3f63-5c73-9ce8-779969cce2d1\",\n                \"vdbImage\": {\n                    \"image\": \"\"\n                }\n            },\n            \"image\": \"\"\n        }\n    ],\n    \"pagination\": {\n        \"totalRecords\": 2,\n        \"currentPage\": 1,\n        \"totalPages\": 1,\n        \"pageSize\": 2,\n        \"nextPage\": null,\n        \"prevPage\": null\n    }\n}"}],"_postman_id":"f5012f15-0fcc-4bb4-8a7f-839e990661c4"},{"name":"Get MCP","id":"a5dc1802-1f39-4605-80aa-142abde91ee6","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps/{{mcp_id}}","description":"<p>Returns one MCP server.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>mcp_id</code></strong> (string): The MCP server ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>mcp</code></strong>: See <strong>Get MCPs</strong>.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","mcps","{{mcp_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"c0babaf0-d20f-405d-8ec6-5ee5aa6cda02","name":"Get MCP - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps/{{mcp_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"mcp\": {\n        \"id\": \"820201bf-8c74-5946-9776-f7653d44c124\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"name\": \"branch managers\",\n        \"description\": \"\",\n        \"type\": \"virtual DB\",\n        \"url\": \"https://platform.pvml.com/platform/api/v1/mcp/workspaces/5a080071-4995-52ef-8dce-9b5ad367a3d4/vdbs/607187ab-3f63-5c73-9ce8-779969cce2d1\",\n        \"createdBy\": \"PVML integration\",\n        \"createdAt\": \"2026-10-30T01:05:49.037363Z\",\n        \"internalReferences\": {\n            \"vdbId\": \"607187ab-3f63-5c73-9ce8-779969cce2d1\",\n            \"vdbImage\": {\n                \"image\": \"\"\n            }\n        },\n        \"image\": \"\"\n    }\n}"}],"_postman_id":"a5dc1802-1f39-4605-80aa-142abde91ee6"},{"name":"Get MCP Tools","id":"fc641fd5-bf4b-4718-8eab-82887535de0b","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps/{{mcp_id}}/tools","description":"<p>Lists the tools of an MCP server, exactly as an AI client sees them.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>mcp_id</code></strong> (string): The MCP server ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>mcpTools</code></strong> (array): <code>name</code>, <code>description</code> (what the AI reads), <code>inputSchema</code> and <code>isAllowed</code>.</li>\n</ul>\n<p>A VDB MCP server has two tools:</p>\n<ul>\n<li><strong><code>describe-vdb-&lt;vdb_id&gt;</code></strong>: returns the schema, descriptions and relations of the VDB.</li>\n<li><strong><code>execute-sql-vdb-&lt;vdb_id&gt;</code></strong>: runs a SQL query. Parameters: <code>query</code> (required) and <code>userQuestion</code> (the user's original question, saved in the audit log).</li>\n</ul>\n<p>💡 The tool descriptions include the VDB name and description. A clear VDB description helps the AI pick the right VDB.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","mcps","{{mcp_id}}","tools"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"e5b78bcd-7b72-44c2-895c-a1c4f4bc948a","name":"Get MCP Tools - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps/{{mcp_id}}/tools"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"mcpTools\": [\n        {\n            \"name\": \"describe-vdb-607187ab-3f63-5c73-9ce8-779969cce2d1\",\n            \"description\": \"Describes the schema, descriptions and relations of the virtual database \\\"branch managers\\\".\",\n            \"inputSchema\": {\n                \"type\": \"object\"\n            },\n            \"isAllowed\": true\n        },\n        {\n            \"name\": \"execute-sql-vdb-607187ab-3f63-5c73-9ce8-779969cce2d1\",\n            \"description\": \"Runs a SQL query on the virtual database \\\"branch managers\\\" and returns the result.\",\n            \"inputSchema\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"query\": {\n                        \"description\": \"The SQL query to run.\",\n                        \"type\": \"string\"\n                    },\n                    \"userQuestion\": {\n                        \"description\": \"The user's original question, saved in the audit log.\",\n                        \"type\": \"string\"\n                    }\n                },\n                \"required\": [\n                    \"query\"\n                ]\n            },\n            \"isAllowed\": true\n        }\n    ]\n}"}],"_postman_id":"fc641fd5-bf4b-4718-8eab-82887535de0b"},{"name":"Get MCP Connection Config","id":"c496d12b-6064-4637-8583-86408df8766a","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps/{{mcp_id}}/connection","description":"<p>Returns a ready-to-use config block for AI clients (the <code>mcpServers</code> format of Cursor, Claude Desktop and others).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>mcp_id</code></strong> (string): The MCP server ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>An object with one key, the server name, and its <code>url</code> and <code>headers</code>.</p>\n<p>💡 The config never contains a real key. Replace <code>&lt;TOKEN_PLACEHOLDER&gt;</code> with <code>Bearer &lt;your API key&gt;</code>, or remove <code>headers</code> to sign in with OAuth.</p>\n<p>Any workspace member with access to the VDB can call it.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","mcps","{{mcp_id}}","connection"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"a52245e8-7825-47e5-af6b-562733df20b5","name":"Get MCP Connection Config - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps/{{mcp_id}}/connection"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"branch managers\": {\n        \"url\": \"https://platform.pvml.com/platform/api/v1/mcp/workspaces/5a080071-4995-52ef-8dce-9b5ad367a3d4/vdbs/607187ab-3f63-5c73-9ce8-779969cce2d1\",\n        \"headers\": {\n            \"Authorization\": \"<TOKEN_PLACEHOLDER>\"\n        }\n    }\n}"}],"_postman_id":"c496d12b-6064-4637-8583-86408df8766a"},{"name":"Get MCP Virtual DB","id":"3465f763-7836-4590-81e2-29071f97d765","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps/{{mcp_id}}/vdb","description":"<p>Returns the VDB behind an MCP server (the opposite of <strong>Get Virtual DB MCP</strong>).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>mcp_id</code></strong> (string): The MCP server ID.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>vdbId</code></strong> (string).</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","mcps","{{mcp_id}}","vdb"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"f8f9c37f-7123-4542-9395-7495a5590c6b","name":"Get MCP Virtual DB - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/mcps/{{mcp_id}}/vdb"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"vdbId\": \"607187ab-3f63-5c73-9ce8-779969cce2d1\"\n}"}],"_postman_id":"3465f763-7836-4590-81e2-29071f97d765"},{"name":"MCP: Initialize","id":"87a13da1-74de-4a4d-a0be-5944a93a31ac","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Accept","value":"application/json, text/event-stream"}],"body":{"mode":"raw","raw":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"initialize\",\n    \"params\": {\n        \"protocolVersion\": \"2025-03-26\",\n        \"capabilities\": {},\n        \"clientInfo\": {\n            \"name\": \"my-ai-app\",\n            \"version\": \"1.0\"\n        }\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/mcp/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}","description":"<p>Starts an MCP session with the MCP server of a VDB. AI clients do this for you. Use this request to test the connection.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>vdb_id</code></strong> (string): The VDB ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>A JSON-RPC <code>initialize</code> message.</p>\n<h3 id=\"response\">Response</h3>\n<ul>\n<li>The server info and capabilities.</li>\n<li>The response <strong>header</strong> <code>Mcp-Session-Id</code>. Send it in the next requests.</li>\n</ul>\n<p>Without a valid sign-in, the response is status 401.</p>\n<p>The user must be a workspace member with access to the VDB.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","mcp","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"8d231f4f-086b-4d8f-960f-d07d0e795a29","name":"MCP: Initialize - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"initialize\",\n    \"params\": {\n        \"protocolVersion\": \"2025-03-26\",\n        \"capabilities\": {},\n        \"clientInfo\": {\n            \"name\": \"my-ai-app\",\n            \"version\": \"1.0\"\n        }\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/mcp/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"result\": {\n        \"protocolVersion\": \"2025-03-26\",\n        \"capabilities\": {\n            \"tools\": {\n                \"listChanged\": true\n            }\n        },\n        \"serverInfo\": {\n            \"name\": \"vdb-transport\",\n            \"version\": \"1.0.0\"\n        }\n    }\n}"}],"_postman_id":"87a13da1-74de-4a4d-a0be-5944a93a31ac"},{"name":"MCP: List Tools","id":"63808fc3-ae18-48a4-b021-46b523a76f98","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Accept","value":"application/json, text/event-stream"},{"key":"Mcp-Session-Id","value":"{{mcp_session_id}}","description":"<p>From the response headers of Initialize. Not needed for Initialize itself.</p>\n"}],"body":{"mode":"raw","raw":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 2,\n    \"method\": \"tools/list\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/mcp/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}","description":"<p>Lists the tools of the VDB MCP server (JSON-RPC <code>tools/list</code>).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>vdb_id</code></strong> (string): The VDB ID.</li>\n</ul>\n<p>Send the <code>Mcp-Session-Id</code> header from <strong>MCP: Initialize</strong>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","mcp","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"3c65b73b-0cbb-4b49-b751-fc4bfb7233c6","name":"MCP: List Tools - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 2,\n    \"method\": \"tools/list\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/mcp/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 2,\n    \"result\": {\n        \"tools\": [\n            {\n                \"description\": \"Describes the schema, descriptions and relations of the virtual database \\\"branch managers\\\".\",\n                \"inputSchema\": {\n                    \"properties\": {},\n                    \"required\": [],\n                    \"type\": \"object\"\n                },\n                \"name\": \"describe-vdb-9e1e9d2e-7b74-5f93-bebd-a5c39d0f8784\"\n            },\n            {\n                \"description\": \"Runs a SQL query on the virtual database \\\"branch managers\\\" and returns the result.\",\n                \"inputSchema\": {\n                    \"properties\": {\n                        \"query\": {\n                            \"description\": \"The SQL query to run.\",\n                            \"type\": \"string\"\n                        },\n                        \"userQuestion\": {\n                            \"description\": \"The user's original question, saved in the audit log.\",\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"required\": [\n                        \"query\"\n                    ],\n                    \"type\": \"object\"\n                },\n                \"name\": \"execute-sql-vdb-9e1e9d2e-7b74-5f93-bebd-a5c39d0f8784\"\n            }\n        ]\n    }\n}"}],"_postman_id":"63808fc3-ae18-48a4-b021-46b523a76f98"},{"name":"MCP: Call Tool","id":"899c1d7c-43dd-40ea-a4ea-8773b82dc21c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Accept","value":"application/json, text/event-stream"},{"key":"Mcp-Session-Id","value":"{{mcp_session_id}}","description":"<p>From the response headers of Initialize. Not needed for Initialize itself.</p>\n"}],"body":{"mode":"raw","raw":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 3,\n    \"method\": \"tools/call\",\n    \"params\": {\n        \"name\": \"execute-sql-vdb-{{vdb_id}}\",\n        \"arguments\": {\n            \"query\": \"select count(*) as accounts from `my-project`.`core`.`accounts`;\",\n            \"userQuestion\": \"How many accounts are there?\"\n        }\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/mcp/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}","description":"<p>Runs a tool (JSON-RPC <code>tools/call</code>). This example runs SQL with <code>execute-sql-vdb-&lt;vdb_id&gt;</code>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>vdb_id</code></strong> (string): The VDB ID.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><code>params.name</code>: The tool name from <strong>MCP: List Tools</strong>.</li>\n<li><code>params.arguments</code>: <code>query</code> (SQL) and <code>userQuestion</code> (the user's original question).</li>\n</ul>\n<h3 id=\"response\">Response</h3>\n<p>The result rows as text, for example <code>[[\"accounts\"],[5000]]</code>. If a policy blocks the query, <code>isError</code> is <code>true</code>.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","mcp","workspaces","{{workspace_id}}","vdbs","{{vdb_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"02118158-3fb7-47aa-8e93-7cd10697c19e","name":"MCP: Call Tool - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 3,\n    \"method\": \"tools/call\",\n    \"params\": {\n        \"name\": \"execute-sql-vdb-{{vdb_id}}\",\n        \"arguments\": {\n            \"query\": \"select count(*) as accounts from `my-project`.`core`.`accounts`;\",\n            \"userQuestion\": \"How many accounts are there?\"\n        }\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/mcp/workspaces/{{workspace_id}}/vdbs/{{vdb_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 3,\n    \"result\": {\n        \"content\": [\n            {\n                \"type\": \"text\",\n                \"text\": \"[[\\\"accounts\\\"],[5000]]\"\n            }\n        ]\n    }\n}"}],"_postman_id":"899c1d7c-43dd-40ea-a4ea-8773b82dc21c"},{"name":"Workspace MCP: List Tools","id":"8f90921a-8b4c-4f2c-82ea-600d35c085ef","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Accept","value":"application/json, text/event-stream"},{"key":"Mcp-Session-Id","value":"{{mcp_session_id}}","description":"<p>From the response headers of Initialize. Not needed for Initialize itself.</p>\n"}],"body":{"mode":"raw","raw":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 2,\n    \"method\": \"tools/list\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/mcp/workspaces/{{workspace_id}}/pvml","description":"<p>Lists the tools of the workspace MCP server (for admins). Start the session first with an <code>initialize</code> message, as in <strong>MCP: Initialize</strong>, but on this URL.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"tools\">Tools</h3>\n<ul>\n<li><code>create-datasource-aggregation-policy</code></li>\n<li><code>create-datasource-column-policy</code></li>\n<li><code>create-datasource-differential-privacy-policy</code></li>\n<li><code>create-datasource-limit-policy</code></li>\n<li><code>create-datasource-masking-policy</code></li>\n<li><code>create-datasource-row-level-policy</code></li>\n<li><code>create-datasource-timeout-policy</code></li>\n<li><code>create-virtual-db</code></li>\n<li><code>delete-datasource-policy</code></li>\n<li><code>delete-virtual-db</code></li>\n<li><code>get-current-user</code></li>\n<li><code>get-datasource-structure</code></li>\n<li><code>get-user-attributes</code></li>\n<li><code>get-virtual-db</code></li>\n<li><code>get-workspace-attributes</code></li>\n<li><code>list-datasource-policies</code></li>\n<li><code>list-datasources</code></li>\n<li><code>list-workspace-groups</code></li>\n<li><code>list-workspace-users</code></li>\n<li><code>update-datasource-aggregation-policy</code></li>\n<li><code>update-datasource-column-policy</code></li>\n<li><code>update-datasource-differential-privacy-policy</code></li>\n<li><code>update-datasource-limit-policy</code></li>\n<li><code>update-datasource-masking-policy</code></li>\n<li><code>update-datasource-row-level-policy</code></li>\n<li><code>update-datasource-timeout-policy</code></li>\n<li><code>update-virtual-db-info</code></li>\n<li><code>update-virtual-db-policies</code></li>\n<li><code>update-virtual-db-users-groups</code></li>\n</ul>\n<p>The example response shows the first tools only.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","mcp","workspaces","{{workspace_id}}","pvml"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"6e3e96a1-add8-41f4-9202-21e775e23198","name":"Workspace MCP: List Tools - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 2,\n    \"method\": \"tools/list\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/mcp/workspaces/{{workspace_id}}/pvml"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 2,\n    \"result\": {\n        \"tools\": [\n            {\n                \"description\": \"Create an aggregation policy that enforces aggregation-only queries for specific schemas\",\n                \"inputSchema\": {\n                    \"properties\": {\n                        \"datasourceName\": {\n                            \"description\": \"Datasource to create the policy on\",\n                            \"type\": \"string\"\n                        },\n                        \"permissionDescription\": {\n                            \"default\": \"\",\n                            \"description\": \"Description for the new policy\",\n                            \"type\": \"string\"\n                        },\n                        \"permissionName\": {\n                            \"description\": \"Name for the new policy\",\n                            \"type\": \"string\"\n                        },\n                        \"schemaName\": {\n                            \"description\": \"List of schema names to apply the aggregation policy to\",\n                            \"items\": {\n                                \"type\": \"string\"\n                            },\n                            \"type\": \"array\"\n                        },\n                        \"tableName\": {\n                            \"description\": \"List of fully qualified table names to apply the aggregation policy to (e.g. catalog.schema.table)\",\n                            \"items\": {\n                                \"type\": \"string\"\n                            },\n                            \"type\": \"array\"\n                        }\n                    },\n                    \"required\": [\n                        \"datasourceName\",\n                        \"permissionName\"\n                    ],\n                    \"type\": \"object\"\n                },\n                \"name\": \"create-datasource-aggregation-policy\"\n            },\n            {\n                \"description\": \"Create a column policy that controls which columns are accessible. Every virtual db requires at least one. Columns are fully qualified (e.g., catalog.schema.table.column); multiple tables can be covered in one policy.\",\n                \"inputSchema\": {\n                    \"properties\": {\n                        \"columns\": {\n                            \"description\": \"Fully qualified column names to allow access to (e.g., catalog.schema.table.column)\",\n                            \"items\": {\n                                \"type\": \"string\"\n                            },\n                            \"type\": \"array\"\n                        },\n                        \"datasourceName\": {\n                            \"description\": \"Datasource to create the policy on\",\n                            \"type\": \"string\"\n                        },\n                        \"permissionDescription\": {\n                            \"default\": \"\",\n                            \"description\": \"Description for the new policy\",\n                            \"type\": \"string\"\n                        },\n                        \"permissionName\": {\n                            \"description\": \"Name for the new policy\",\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"required\": [\n                        \"datasourceName\",\n                        \"permissionName\",\n                        \"columns\"\n                    ],\n                    \"type\": \"object\"\n                },\n                \"name\": \"create-datasource-column-policy\"\n            },\n            {\n                \"description\": \"Create a differential-privacy policy that adds statistical noise to query results\",\n                \"inputSchema\": {\n                    \"properties\": {\n                        \"datasourceName\": {\n                            \"description\": \"Datasource to create the policy on\",\n                            \"type\": \"string\"\n                        },\n                        \"permissionDescription\": {\n                            \"default\": \"\",\n                            \"description\": \"Description for the new policy\",\n                            \"type\": \"string\"\n                        },\n                        \"permissionName\": {\n                            \"description\": \"Name for the new policy\",\n                            \"type\": \"string\"\n                        },\n                        \"schemaName\": {\n                            \"description\": \"List of schema names to apply differential-privacy to\",\n                            \"items\": {\n                                \"type\": \"string\"\n                            },\n                            \"type\": \"array\"\n                        }\n                    },\n                    \"required\": [\n                        \"datasourceName\",\n                        \"permissionName\",\n                        \"schemaName\"\n                    ],\n                    \"type\": \"object\"\n                },\n                \"name\": \"create-datasource-differential-privacy-policy\"\n            }\n        ]\n    }\n}"}],"_postman_id":"8f90921a-8b4c-4f2c-82ea-600d35c085ef"}],"id":"4c5f8b21-24a0-45d6-9bcc-506bd78e8bad","description":"<p><strong>MCP (Model Context Protocol)</strong> is the standard way for AI applications to use tools, such as ChatGPT, Claude, Cursor, Copilot Studio or your own agent. PVML gives you two kinds of MCP servers.</p>\n<h3 id=\"1-the-mcp-server-of-a-vdb-for-data-questions\">1. The MCP server of a VDB (for data questions)</h3>\n<p><strong>Every VDB gets its own MCP server automatically.</strong> An AI client that connects to it can:</p>\n<ul>\n<li><strong>describe</strong> the VDB: the tables, columns, descriptions and relations the user can see, and</li>\n<li><strong>run SQL</strong> on the VDB.</li>\n</ul>\n<p>The AI works <strong>as the user who connected</strong>. It gets exactly the same column, row, masking and other policies as that user in a BI tool. The AI can not see more than the user, and every query is in the audit log, including the user's original question. This is the logic of PVML: one set of policies, for every client.</p>\n<p><strong>URL:</strong> <code>https://platform.pvml.com/platform/api/v1/mcp/workspaces/{workspace_id}/vdbs/{vdb_id}</code></p>\n<h3 id=\"2-the-workspace-mcp-server-for-admins\">2. The workspace MCP server (for admins)</h3>\n<p>With this server, an admin can <strong>manage PVML with AI</strong>, for example: \"Create a VDB for the sales team with access to the orders table, and mask the email column\". It has tools to list datasources and users, and to create and change VDBs and policies. Only workspace admins and owners can use it.</p>\n<p><strong>URL:</strong> <code>https://platform.pvml.com/platform/api/v1/mcp/workspaces/{workspace_id}/pvml</code></p>\n<h3 id=\"how-ai-clients-sign-in\">How AI clients sign in</h3>\n<ul>\n<li><strong>OAuth (recommended for people):</strong> most AI clients support it. Add the URL in the client. The client opens a PVML sign-in page in the browser, and the user logs in to PVML as usual.</li>\n<li><strong>API key (for scripts and servers):</strong> send <code>Authorization: Bearer &lt;API key&gt;</code>. The key acts as its user, so give it to a service account with only the access it needs.</li>\n</ul>\n<h3 id=\"how-to-connect-a-client\">How to connect a client</h3>\n<ol>\n<li>Find the VDB's MCP server: <strong>Get Virtual DB MCP</strong> returns its <code>mcpId</code>.</li>\n<li><strong>Get MCP Connection Config</strong> returns a ready config block. Paste it in your AI client and replace <code>&lt;TOKEN_PLACEHOLDER&gt;</code> with <code>Bearer &lt;your API key&gt;</code>, or remove the header to use OAuth.</li>\n</ol>\n<h3 id=\"tips-for-better-ai-answers\">Tips for better AI answers</h3>\n<ul>\n<li>Add <strong>descriptions</strong> and <strong>relations</strong> to the datasource (see <strong>Datasources</strong>). The AI reads them when it describes the VDB.</li>\n<li>Keep each VDB small and focused on one task. The AI gets less noise and uses fewer tokens.</li>\n</ul>\n<h3 id=\"try-it-in-postman\">Try it in Postman</h3>\n<p>The requests <strong>MCP: Initialize</strong>, <strong>MCP: List Tools</strong> and <strong>MCP: Call Tool</strong> show the protocol step by step. MCP uses JSON-RPC over HTTP (\"streamable HTTP\"). Copy the <code>Mcp-Session-Id</code> response header from Initialize into the <code>mcp_session_id</code> variable.</p>\n","_postman_id":"4c5f8b21-24a0-45d6-9bcc-506bd78e8bad","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"LLMs","item":[{"name":"Create LLM - OpenAI","id":"843ceed6-540e-4418-b895-c0adee5b718f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"workspace gpt\",\n    \"description\": \"system LLM for the workspace\",\n    \"workspaceId\": \"{{workspace_id}}\",\n    \"vendorName\": \"openAi\",\n    \"modelName\": \"gpt-4.1-2025-04-14\",\n    \"token\": \"{{openai_api_key}}\",\n    \"llmProps\": {\n        \"temperature\": 0.7,\n        \"maxTokens\": null\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms","description":"<p>Connects an LLM to the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>name</code></strong> (string): A name for the LLM.</li>\n<li><strong><code>description</code></strong> (string): A short description.</li>\n<li><strong><code>workspaceId</code></strong> (string): The workspace ID.</li>\n<li><strong><code>vendorName</code></strong> (string): The vendor, from <strong>Get Supported LLMs</strong> (for example <code>openAi</code>, <code>anthropic</code>, <code>databricks</code>).</li>\n<li><strong><code>modelName</code></strong> (string): A model of that vendor, from <strong>Get Supported LLMs</strong>.</li>\n<li><strong><code>token</code></strong> (string): The API key of the model provider. On update, send <code>\"\"</code> to keep the current key.</li>\n<li><strong><code>llmProps</code></strong> (object): Model settings. The keys depend on the vendor:<ul>\n<li>OpenAI: <code>temperature</code>, <code>maxTokens</code>.</li>\n<li>Anthropic: <code>maxTokens</code>, <code>reasoningEffort</code>, <code>thinking</code>, <code>temperature</code> (optional).</li>\n<li>Databricks: <code>temperature</code>, <code>url</code> (the serving endpoint URL).</li>\n<li>Azure: <code>azure_endpoint</code>, <code>deploymentName</code>, <code>openai_api_version</code>, <code>temperature</code>.</li>\n<li>OpenAI-compatible and NVIDIA NIM: <code>base_url</code>, <code>model_name</code>, <code>maxTokens</code>.</li>\n<li>Custom: any key-value pairs your model needs.</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The LLM object: <code>id</code>, <code>name</code>, <code>description</code>, <code>workspaceId</code>, <code>vendorName</code>, <code>modelName</code>, <code>url</code>, <code>llmProps</code>, <code>createTime</code> and <code>lastModifiedAt</code> (Unix time in milliseconds). The <code>token</code> is never returned.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","llms"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"8ed575d7-9f57-4aee-8a08-849259eb64d8","name":"Create LLM - OpenAI - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"workspace gpt\",\n    \"description\": \"system LLM for the workspace\",\n    \"workspaceId\": \"{{workspace_id}}\",\n    \"vendorName\": \"openAi\",\n    \"modelName\": \"gpt-4.1-2025-04-14\",\n    \"token\": \"{{openai_api_key}}\",\n    \"llmProps\": {\n        \"temperature\": 0.7,\n        \"maxTokens\": null\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"0590b96c-c4a3-5232-a0db-e9a3f8051003\",\n    \"name\": \"workspace gpt\",\n    \"description\": \"system LLM for the workspace\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"vendorName\": \"openAi\",\n    \"modelName\": \"gpt-4.1-2025-04-14\",\n    \"url\": \"\",\n    \"llmProps\": {\n        \"maxTokens\": null,\n        \"temperature\": 0.7\n    },\n    \"createTime\": 1793318168245,\n    \"lastModifiedAt\": 1793318168245\n}"}],"_postman_id":"843ceed6-540e-4418-b895-c0adee5b718f"},{"name":"Create LLM - Databricks","id":"b7895de5-e24c-4276-b2b9-54da75319e03","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"databricks llama\",\n    \"description\": \"\",\n    \"workspaceId\": \"{{workspace_id}}\",\n    \"vendorName\": \"databricks\",\n    \"modelName\": \"databricks-meta-llama-3-1-70b-instruct\",\n    \"token\": \"{{databricks_llm_token}}\",\n    \"llmProps\": {\n        \"temperature\": 0.7,\n        \"url\": \"{{databricks_llm_url}}\"\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms","description":"<p>Connects a Databricks model serving endpoint as an LLM. See <strong>Create LLM - OpenAI</strong> for all fields.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>Same as <strong>Create LLM - OpenAI</strong>, with <code>vendorName</code> <code>databricks</code> and <code>llmProps</code>:</p>\n<ul>\n<li><strong><code>temperature</code></strong> (number).</li>\n<li><strong><code>url</code></strong> (string): The serving endpoint URL.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The LLM object: <code>id</code>, <code>name</code>, <code>description</code>, <code>workspaceId</code>, <code>vendorName</code>, <code>modelName</code>, <code>url</code>, <code>llmProps</code>, <code>createTime</code> and <code>lastModifiedAt</code> (Unix time in milliseconds). The <code>token</code> is never returned.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","llms"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"5bcbf3f9-c3dc-4930-a5a0-aedf79059b9a","name":"Create LLM - Databricks - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"databricks llama\",\n    \"description\": \"\",\n    \"workspaceId\": \"{{workspace_id}}\",\n    \"vendorName\": \"databricks\",\n    \"modelName\": \"databricks-meta-llama-3-1-70b-instruct\",\n    \"token\": \"{{databricks_llm_token}}\",\n    \"llmProps\": {\n        \"temperature\": 0.7,\n        \"url\": \"{{databricks_llm_url}}\"\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"c490391e-c2d5-54f6-b844-cef951af7a36\",\n    \"name\": \"databricks llama\",\n    \"description\": \"\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"vendorName\": \"databricks\",\n    \"modelName\": \"databricks-meta-llama-3-1-70b-instruct\",\n    \"url\": \"\",\n    \"llmProps\": {\n        \"temperature\": 0.7,\n        \"url\": \"https://<your-databricks-host>/serving-endpoints/<endpoint>/invocations\"\n    },\n    \"createTime\": 1793318168245,\n    \"lastModifiedAt\": 1793318168245\n}"}],"_postman_id":"b7895de5-e24c-4276-b2b9-54da75319e03"},{"name":"Get LLM","id":"022b1ef1-52d1-4caa-bda1-793bb3cadc5f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms/{{llm_id}}","description":"<p>Returns one LLM.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>llm_id</code></strong> (string): The unique identifier of the LLM.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The LLM object: <code>id</code>, <code>name</code>, <code>description</code>, <code>workspaceId</code>, <code>vendorName</code>, <code>modelName</code>, <code>url</code>, <code>llmProps</code>, <code>createTime</code> and <code>lastModifiedAt</code> (Unix time in milliseconds). The <code>token</code> is never returned.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","llms","{{llm_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"f8972da1-fa6f-4c22-a525-29c12a310ede","name":"Get LLM - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms/{{llm_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"0590b96c-c4a3-5232-a0db-e9a3f8051003\",\n    \"name\": \"workspace gpt\",\n    \"description\": \"system LLM for the workspace\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"vendorName\": \"openAi\",\n    \"modelName\": \"gpt-4.1-2025-04-14\",\n    \"url\": \"\",\n    \"llmProps\": {\n        \"temperature\": 0.5\n    },\n    \"createTime\": 1793318168245,\n    \"lastModifiedAt\": 1793318168425\n}"}],"_postman_id":"022b1ef1-52d1-4caa-bda1-793bb3cadc5f"},{"name":"Get LLMs","id":"3f0e536c-1ae7-4c57-8d19-6f514e0fdd5d","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms","description":"<p>Returns all LLMs in the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>workspaceLlms</code></strong> (array): LLM objects (see <strong>Get LLM</strong>).</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","llms"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"4fb2472f-19ee-4c0b-ac1b-5fcff06b231e","name":"Get LLMs - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"workspaceLlms\": [\n        {\n            \"id\": \"0590b96c-c4a3-5232-a0db-e9a3f8051003\",\n            \"name\": \"workspace gpt\",\n            \"description\": \"system LLM for the workspace\",\n            \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n            \"vendorName\": \"openAi\",\n            \"modelName\": \"gpt-4.1-2025-04-14\",\n            \"url\": \"\",\n            \"llmProps\": {\n                \"temperature\": 0.5\n            },\n            \"createTime\": 1793318168245,\n            \"lastModifiedAt\": 1793318168425\n        }\n    ]\n}"}],"_postman_id":"3f0e536c-1ae7-4c57-8d19-6f514e0fdd5d"},{"name":"Get Supported LLMs","id":"cf6feb18-f4ea-484b-9d8a-29a96995991b","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/llms","description":"<p>Returns the LLM vendors and models PVML supports. Use these values in <code>vendorName</code> and <code>modelName</code>.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>keysOrder</code></strong> (array): The vendors, in display order.</li>\n<li><strong><code>data</code></strong> (object): For each vendor, the list of model names.</li>\n</ul>\n<p>The example shows only part of the list.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","llms"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"e21710dd-f847-41a7-b6b7-7e90cb50150f","name":"Get Supported LLMs - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/llms"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"keysOrder\": [\n        \"openAi\",\n        \"anthropic\",\n        \"google\",\n        \"azure\",\n        \"databricks\",\n        \"openAiCompatible\",\n        \"nvidia\",\n        \"custom\"\n    ],\n    \"data\": {\n        \"openAi\": [\n            \"gpt-4.1-2025-04-14\",\n            \"gpt-5-2025-08-07\",\n            \"gpt-5-mini-2025-08-07\"\n        ],\n        \"anthropic\": [\n            \"claude-sonnet-4-6\",\n            \"claude-haiku-4-5-20251001\"\n        ],\n        \"google\": [\n            \"gemini-2.5-pro\",\n            \"gemini-2.5-flash\"\n        ],\n        \"azure\": [\n            \"gpt\"\n        ],\n        \"databricks\": [\n            \"databricks-meta-llama-3-1-70b-instruct\"\n        ],\n        \"openAiCompatible\": [\n            \"open-ai-compatible-completions\"\n        ],\n        \"nvidia\": [\n            \"nim\"\n        ],\n        \"custom\": [\n            \"custom\"\n        ]\n    }\n}"}],"_postman_id":"cf6feb18-f4ea-484b-9d8a-29a96995991b"},{"name":"Update LLM - Token properties","id":"90846403-b90c-43d6-9052-2b79de0ccf0f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"workspace gpt\",\n    \"description\": \"system LLM for the workspace\",\n    \"workspaceId\": \"{{workspace_id}}\",\n    \"vendorName\": \"openAi\",\n    \"modelName\": \"gpt-4.1-2025-04-14\",\n    \"id\": \"{{llm_id}}\",\n    \"token\": \"{{openai_api_key}}\",\n    \"llmProps\": {\n        \"temperature\": 0.5\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms/{{llm_id}}","description":"<p>Updates an LLM and replaces its API key.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>llm_id</code></strong> (string): The unique identifier of the LLM.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>The full LLM object, the same fields as <strong>Create LLM - OpenAI</strong>, plus <strong><code>id</code></strong>. Fields you leave out are reset.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The LLM object: <code>id</code>, <code>name</code>, <code>description</code>, <code>workspaceId</code>, <code>vendorName</code>, <code>modelName</code>, <code>url</code>, <code>llmProps</code>, <code>createTime</code> and <code>lastModifiedAt</code> (Unix time in milliseconds). The <code>token</code> is never returned.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","llms","{{llm_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"b2f2303c-4a43-4cfc-a7bc-215ad9ff6743","name":"Update LLM - Token properties - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"workspace gpt\",\n    \"description\": \"system LLM for the workspace\",\n    \"workspaceId\": \"{{workspace_id}}\",\n    \"vendorName\": \"openAi\",\n    \"modelName\": \"gpt-4.1-2025-04-14\",\n    \"id\": \"{{llm_id}}\",\n    \"token\": \"{{openai_api_key}}\",\n    \"llmProps\": {\n        \"temperature\": 0.5\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms/{{llm_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"0590b96c-c4a3-5232-a0db-e9a3f8051003\",\n    \"name\": \"workspace gpt\",\n    \"description\": \"system LLM for the workspace\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"vendorName\": \"openAi\",\n    \"modelName\": \"gpt-4.1-2025-04-14\",\n    \"url\": \"\",\n    \"llmProps\": {\n        \"temperature\": 0.5\n    },\n    \"createTime\": 1793318168245,\n    \"lastModifiedAt\": 1793318168425\n}"}],"_postman_id":"90846403-b90c-43d6-9052-2b79de0ccf0f"},{"name":"Update LLM - Details only","id":"42b81c19-e766-471e-bbc1-e6c2ddc2a687","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"workspace gpt\",\n    \"description\": \"system LLM for the workspace\",\n    \"workspaceId\": \"{{workspace_id}}\",\n    \"vendorName\": \"openAi\",\n    \"modelName\": \"gpt-4.1-2025-04-14\",\n    \"id\": \"{{llm_id}}\",\n    \"token\": \"\",\n    \"llmProps\": {\n        \"temperature\": 0.5\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms/{{llm_id}}","description":"<p>Updates an LLM and keeps its API key (send <code>\"token\": \"\"</code>).</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>llm_id</code></strong> (string): The unique identifier of the LLM.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<p>The full LLM object, the same fields as <strong>Create LLM - OpenAI</strong>, plus <strong><code>id</code></strong>. Fields you leave out are reset.</p>\n<h3 id=\"response-body\">Response Body</h3>\n<p>The LLM object: <code>id</code>, <code>name</code>, <code>description</code>, <code>workspaceId</code>, <code>vendorName</code>, <code>modelName</code>, <code>url</code>, <code>llmProps</code>, <code>createTime</code> and <code>lastModifiedAt</code> (Unix time in milliseconds). The <code>token</code> is never returned.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","llms","{{llm_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"6165ca9c-fd58-4a23-b101-33e470bdb003","name":"Update LLM - Details only - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"name\": \"workspace gpt\",\n    \"description\": \"system LLM for the workspace\",\n    \"workspaceId\": \"{{workspace_id}}\",\n    \"vendorName\": \"openAi\",\n    \"modelName\": \"gpt-4.1-2025-04-14\",\n    \"id\": \"{{llm_id}}\",\n    \"token\": \"\",\n    \"llmProps\": {\n        \"temperature\": 0.5\n    }\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms/{{llm_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": \"0590b96c-c4a3-5232-a0db-e9a3f8051003\",\n    \"name\": \"workspace gpt\",\n    \"description\": \"system LLM for the workspace\",\n    \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n    \"vendorName\": \"openAi\",\n    \"modelName\": \"gpt-4.1-2025-04-14\",\n    \"url\": \"\",\n    \"llmProps\": {\n        \"temperature\": 0.5\n    },\n    \"createTime\": 1793318168245,\n    \"lastModifiedAt\": 1793318168425\n}"}],"_postman_id":"42b81c19-e766-471e-bbc1-e6c2ddc2a687"},{"name":"Delete LLM","id":"bdfa393f-148f-487a-bf01-0f86cd6d1e74","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms/{{llm_id}}","description":"<p>Deletes an LLM.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n<li><strong><code>llm_id</code></strong> (string): The unique identifier of the LLM.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>⚠️ You can not delete the LLM while it is the System LLM. Call <strong>Remove System LLM</strong> first.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","llms","{{llm_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"0c9a7ce5-e371-4cc5-a401-fae22dddeb65","name":"Delete LLM - 200 OK","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/llms/{{llm_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"bdfa393f-148f-487a-bf01-0f86cd6d1e74"},{"name":"Get System LLM","id":"27d4fc78-d240-4aa5-80ba-9d0da9b6d7e5","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/systemLlm","description":"<p>Returns the System LLM of the workspace.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>llmId</code></strong> (string): The ID of the System LLM. Empty (<code>\"\"</code>) means PVML uses its internal model.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","systemLlm"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"169382f8-d9fa-44ac-ac1f-e42ec252479c","name":"Get System LLM - 200 Custom LLM","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/systemLlm"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"llmId\": \"0590b96c-c4a3-5232-a0db-e9a3f8051003\"\n}"},{"id":"88955b18-304d-4099-a367-d161cf097b32","name":"Get System LLM - 200 PVML internal","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/systemLlm"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"llmId\": \"\"\n}"}],"_postman_id":"27d4fc78-d240-4aa5-80ba-9d0da9b6d7e5"},{"name":"Set System LLM","id":"ed3cf9d5-c3bf-4eae-9647-de927536469f","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"llmId\": \"{{llm_id}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/systemLlm","description":"<p>Sets an LLM of the workspace as the System LLM. PVML uses it for the PII scan, Scan Descriptions and Suggest Relations.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><strong><code>llmId</code></strong> (string): The LLM ID, from <strong>Create LLM</strong> or <strong>Get LLMs</strong>.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","systemLlm"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"0a3912d8-43c2-473f-b40f-3ffd1fdb2a39","name":"Set System LLM - 200 OK","originalRequest":{"method":"POST","header":[],"body":{"mode":"raw","raw":"{\n    \"llmId\": \"{{llm_id}}\"\n}","options":{"raw":{"language":"json"}}},"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/systemLlm"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"ed3cf9d5-c3bf-4eae-9647-de927536469f"},{"name":"Remove System LLM","id":"719ba18d-2696-45c9-a447-a21e565eb712","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/systemLlm","description":"<p>Removes the System LLM. PVML goes back to its internal model. The LLM itself is not deleted.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<p>Status 200 with an empty body.</p>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","systemLlm"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"d0591673-85ba-4c46-8c57-2b41c464770c","name":"Remove System LLM - 200 OK","originalRequest":{"method":"DELETE","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/systemLlm"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":""}],"_postman_id":"719ba18d-2696-45c9-a447-a21e565eb712"}],"id":"cf464df8-5110-4e4d-86d1-0d06809a530f","description":"<p>An LLM is a connection to a language model of your choice (OpenAI, Anthropic, Google, Azure, Databricks, an OpenAI-compatible server, and more). Use <strong>Get Supported LLMs</strong> to see the vendors and models you can use.</p>\n<p>PVML uses an LLM only for three helper features: the <strong>PII scan</strong>, <strong>Scan Descriptions</strong> and <strong>Suggest Relations</strong>. <strong>Policy enforcement never uses an LLM</strong>: every query is checked by deterministic rules. This is the <strong>System LLM</strong>. If you do not set one, PVML uses its internal model.</p>\n<p><strong>How to use your own model:</strong></p>\n<ol>\n<li>Create an LLM with <strong>Create LLM</strong>.</li>\n<li>Set it as the workspace System LLM with <strong>Set System LLM</strong>.</li>\n<li>To go back to the PVML internal model, call <strong>Remove System LLM</strong>.</li>\n</ol>\n<p>In the PVML UI, the System LLM is under <strong>Settings &gt; General</strong>:</p>\n<p>[SCREENSHOT: system_llm.png]</p>\n<p>💡 The <code>token</code> (your model API key) is saved securely and is never returned in responses.</p>\n<p>💡 Make sure the API key is valid before you save the LLM.</p>\n","_postman_id":"cf464df8-5110-4e4d-86d1-0d06809a530f","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}}],"id":"e16d2861-faa7-4237-940f-ee527c49e433","description":"<img src=\"https://content.pstmn.io/e03a30bb-4014-4d01-b372-0ba8a63cdb31/UFZNTCBJbmZvZ3JhcGhpY3MgcHJlc2VudGF0aW9uICgxKS5wbmc=\" />\n\n<p>PVML gives AI applications secure access to your data. Every VDB is exposed as an <strong>MCP server</strong>, so any AI client can query it with the same policies and audit as any other client.</p>\n<p>To help AI clients write correct queries, each VDB comes with a <strong>Semantic Layer</strong>.</p>\n<h3 id=\"semantic-layer\">Semantic Layer</h3>\n<p>When an AI client describes a VDB through MCP, PVML returns only what the VDB allows: its tables and columns, with their <strong>descriptions</strong> and <strong>relations</strong>. This is the Semantic Layer.</p>\n<p>Benefits:</p>\n<ol>\n<li><strong>Fewer tokens:</strong> the AI client gets only the tables of this VDB, not the whole database.</li>\n<li><strong>Better answers:</strong> a small, focused schema with clear descriptions helps the AI client pick the right tables and joins.</li>\n<li><strong>Compliance:</strong> columns that the VDB does not allow (for example <code>gender</code> for a loan team) never reach the AI client.</li>\n</ol>\n<p>The VDB is <strong>consumed as an MCP</strong>. It defines the scope of data and metadata that the AI client can see.</p>\n<img src=\"https://content.pstmn.io/87c212b3-1ce4-4c91-ab17-49d5ea98b7d4/QVBJIGltYWdlcyAoMykucG5n\" alt=\"The%20Semantic%20Layer%20(descriptions)\" />\n\n<h3 id=\"system-llm\">System LLM</h3>\n<p>Some PVML features use an LLM: the <strong>PII scan</strong>, <strong>Scan Descriptions</strong> and <strong>Suggest Relations</strong>. By default PVML uses its internal model. You can connect your own model and set it as the workspace <strong>System LLM</strong>. See the <strong>LLMs</strong> section below.</p>\n<p>All queries from AI clients go through the PVML engine. Results always follow each user's permissions.</p>\n","_postman_id":"e16d2861-faa7-4237-940f-ee527c49e433","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}},{"name":"Audit","item":[{"name":"Get Audit Records","id":"dee99b86-ff6d-435b-b5f0-4f1bc634b5cf","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/query-audit?projectionColumns=id,query,duration,source,startTime,status,endTime,userEmail,datasourceType,userQuestion&pageSize=15&pageNumber=1","description":"<p>Returns query records from the audit. In the platform: <strong>Data Access Audit</strong>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>projectionColumns</code></strong> (string, query): A comma-separated list of specific columns to include in the response. Example values include <code>id</code>, <code>query</code>, <code>duration</code>, <code>source</code>, <code>startTime</code>, <code>status</code>, <code>endTime</code>, <code>userEmail</code>, <code>datasourceType</code>, <code>userQuestion</code>.</p>\n</li>\n<li><p><strong><code>pageSize</code></strong> (number, query): The number of results to return per page. Defaults to 15.</p>\n</li>\n<li><p><strong><code>pageNumber</code></strong> (number, query): The page number of results to retrieve. Defaults to 1.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p><strong>data</strong> (array): A list of audit entries, each containing details about a specific audited operation:</p>\n<ul>\n<li><p><strong>startTime</strong> (timestamp, ISO 8601): The time when the query execution started.</p>\n</li>\n<li><p><strong>status</strong> (string): The status of the query execution (e.g., Success, Failure).</p>\n</li>\n<li><p><strong>endTime</strong> (timestamp, ISO 8601): The time when the query execution ended.</p>\n</li>\n<li><p><strong>duration</strong> (number): The duration of the query execution in milliseconds.</p>\n</li>\n<li><p><strong>query</strong> (string): The SQL query that was executed.</p>\n</li>\n<li><p><strong>userEmail</strong> (string): The email address of the user who executed the query.</p>\n</li>\n<li><p><strong>datasourceType</strong> (string): The type of data source used for the query (e.g., Databricks, Snowflake).</p>\n</li>\n<li><p><strong>source</strong> (string): The source from which the query was initiated (e.g., Connector, API).</p>\n</li>\n<li><p><strong>id</strong> (string): The unique identifier for the query or operation.</p>\n</li>\n</ul>\n<p><strong>pagination</strong> (object): Contains pagination details for the response.</p>\n<ul>\n<li><p><strong>totalRecords</strong> (number): The total number of query audit entries available.</p>\n</li>\n<li><p><strong>currentPage</strong> (number): The current page number in the results.</p>\n</li>\n<li><p><strong>totalPages</strong> (number): The total number of pages available.</p>\n</li>\n<li><p><strong>pageSize</strong> (number): The number of query audit entries returned per page.</p>\n</li>\n<li><p><strong>nextPage</strong> (number or null): The page number of the next page, or <code>null</code> if there are no more pages.</p>\n</li>\n<li><p><strong>prevPage</strong> (number or null): The page number of the previous page, or <code>null</code> if it is the first page.</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","query-audit"],"host":["platform","pvml","com"],"query":[{"key":"projectionColumns","value":"id,query,duration,source,startTime,status,endTime,userEmail,datasourceType,userQuestion"},{"key":"pageSize","value":"15"},{"key":"pageNumber","value":"1"}],"variable":[]}},"response":[{"id":"47f57b29-fd6e-4cdf-bd7e-560615b0d77b","name":"Get Audit Records - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/query-audit?projectionColumns=id,query,duration,source,startTime,status,endTime,userEmail,datasourceType,userQuestion&pageSize=15&pageNumber=1","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","query-audit"],"query":[{"key":"projectionColumns","value":"id,query,duration,source,startTime,status,endTime,userEmail,datasourceType,userQuestion"},{"key":"pageSize","value":"15"},{"key":"pageNumber","value":"1"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"data\": [\n        {\n            \"startTime\": \"2026-10-29T23:56:02.258Z\",\n            \"status\": \"Success\",\n            \"endTime\": \"2026-10-29T23:56:02.725Z\",\n            \"duration\": 467,\n            \"query\": \"select gender, count(*) as patients from `my-project`.`clinic`.`patients` group by gender\",\n            \"userEmail\": \"jane.doe@example.com\",\n            \"datasourceType\": \"BigQuery\",\n            \"source\": \"Api\",\n            \"id\": \"6f0c95f1ff727fedbb2736da\"\n        },\n        {\n            \"startTime\": \"2026-10-29T23:46:34.172Z\",\n            \"status\": \"Success\",\n            \"endTime\": \"2026-10-29T23:46:34.699Z\",\n            \"duration\": 527,\n            \"query\": \"select id, name from `my-project`.`clinic`.`doctors` limit 3\",\n            \"userEmail\": \"jane.doe@example.com\",\n            \"datasourceType\": \"BigQuery\",\n            \"source\": \"Api\",\n            \"id\": \"6f0c7d5f0c274d5e8a18f4c9\"\n        }\n    ],\n    \"pagination\": {\n        \"nextCursor\": \"eyJwYWdlIjoyfQ\"\n    }\n}"}],"_postman_id":"dee99b86-ff6d-435b-b5f0-4f1bc634b5cf"},{"name":"Get Audit Record","id":"e51fd77b-d00f-4333-89e4-bc90c2046431","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/query-audit/{{audit_record_id}}","description":"<p>Returns one query record from the audit. In the platform: <strong>Data Access Audit &gt; Record Details</strong>.</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><p><strong><code>workspace_id</code></strong>(string): The unique identifier of the workspace.</p>\n</li>\n<li><p><strong><code>audit_record_id</code></strong> (string): The unique identifier of the audit record.</p>\n</li>\n</ul>\n<h3 id=\"request-body\"><strong>Request Body</strong></h3>\n<p>This request does not contain a request body.</p>\n<h3 id=\"response-body\"><strong>Response Body</strong></h3>\n<p><strong>data</strong> (object): Contains detailed information about the record, including:</p>\n<ul>\n<li><p><strong>startTime</strong> (timestamp, ISO 8601): The start time of the operation.</p>\n</li>\n<li><p><strong>status</strong> (string): The current status of the operation (e.g., Success, Failure).</p>\n</li>\n<li><p><strong>endTime</strong> (timestamp, ISO 8601): The end time of the operation.</p>\n</li>\n<li><p><strong>duration</strong> (number): The duration of the operation in milliseconds.</p>\n</li>\n<li><p><strong>query</strong> (string): The SQL query executed during the operation.</p>\n</li>\n<li><p><strong>userId</strong> (string): Unique identifier for the user who executed the operation.</p>\n</li>\n<li><p><strong>userEmail</strong> (string): Email address of the user who executed the operation.</p>\n</li>\n<li><p><strong>datasourceId</strong> (string): Unique identifier of the data source used in the operation.</p>\n</li>\n<li><p><strong>datasourceType</strong> (string): The type of data source.</p>\n</li>\n<li><p><strong>source</strong> (string): The source from which the operation was initiated (e.g., Connector, API).</p>\n</li>\n<li><p><strong>traceId</strong> (string): Unique identifier used for tracing the operation.</p>\n</li>\n<li><p><strong>id</strong> (string): Unique identifier for the audit record.</p>\n</li>\n<li><p><strong>workspaceId</strong> (string): Unique identifier of the workspace where the operation occurred.</p>\n</li>\n<li><p><strong>vdbId</strong> (string): Unique identifier for the VDB with which the query was executed.</p>\n</li>\n<li><p><strong>vdbName</strong> (string): Name of the VDB with which the query was executed.</p>\n</li>\n</ul>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","query-audit","{{audit_record_id}}"],"host":["platform","pvml","com"],"query":[],"variable":[]}},"response":[{"id":"f8832f60-08f8-40ed-a255-151663a5b10b","name":"Get Audit Record - 200 OK","originalRequest":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/query-audit/{{audit_record_id}}"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"data\": {\n        \"startTime\": \"2026-10-29T23:56:02.258Z\",\n        \"status\": \"Success\",\n        \"endTime\": \"2026-10-29T23:56:02.725Z\",\n        \"permissions\": {\n            \"accessPermissions\": [\n                {\n                    \"schema\": \"*\",\n                    \"table\": \"*\",\n                    \"column\": \"*\"\n                }\n            ],\n            \"privacyPermission\": {\n                \"schemas\": null\n            },\n            \"filterPermissionPaths\": [\n                {\n                    \"predicate\": \"`my-project`.`clinic`.`patients`.`gender`='F'\",\n                    \"scope\": [\n                        \"`my-project`.`clinic`.`patients`\"\n                    ],\n                    \"columns\": [\n                        \"`my-project`.`clinic`.`patients`.`gender`\"\n                    ]\n                }\n            ]\n        },\n        \"duration\": 467,\n        \"query\": \"select gender, count(*) as patients from `my-project`.`clinic`.`patients` group by gender\",\n        \"userId\": \"4599af59-6602-58a4-97e0-a0b5016bcc58\",\n        \"userEmail\": \"jane.doe@example.com\",\n        \"datasourceId\": \"669ba481-7068-535e-88fd-6626e4d492af\",\n        \"datasourceName\": \"MyDB\",\n        \"datasourceType\": \"BigQuery\",\n        \"source\": \"Api\",\n        \"traceId\": \"7567540024779538534\",\n        \"id\": \"6f0c95f1ff727fedbb2736da\",\n        \"workspaceId\": \"5a080071-4995-52ef-8dce-9b5ad367a3d4\",\n        \"vdbId\": \"df8fe2c3-7c75-5f3e-8d47-d08181298485\",\n        \"vdbName\": \"clinic analytics\"\n    }\n}"}],"_postman_id":"e51fd77b-d00f-4333-89e4-bc90c2046431"},{"name":"Get Activity Log","id":"fb2df94d-65b0-4cf9-8cd3-2a9f5a89a15c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/workspace-logs?pageSize=50","description":"<p>Returns the activity log of the workspace. In the platform: <strong>System Audit</strong>. It shows who changed what, and when. For example, who created a policy, changed a VDB or revoked a key. (Queries are in <strong>Get Audit Records</strong>.)</p>\n<h3 id=\"path-parameters\">Path Parameters</h3>\n<ul>\n<li><strong><code>workspace_id</code></strong> (string): The unique identifier of the workspace.</li>\n</ul>\n<h3 id=\"query-parameters\">Query Parameters</h3>\n<ul>\n<li><strong><code>pageSize</code></strong> (number, max 500): Items per page.</li>\n<li><strong><code>cursor</code></strong> (string): The <code>nextCursor</code> from the previous page.</li>\n<li><strong>Filters:</strong> use a field name as the parameter, and a JSON value <code>{\"operator\": \"eq|gte|lte|like\", \"value\": ...}</code>. For example:<ul>\n<li><code>action={\"operator\":\"eq\",\"value\":\"update\"}</code></li>\n<li><code>userEmail={\"operator\":\"eq\",\"value\":\"dana@example.com\"}</code></li>\n<li><code>timestamp={\"operator\":\"gte\",\"value\":\"2026-10-01T00:00:00.000Z\"}</code> (repeat with <code>lte</code> for a range)</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"response-body\">Response Body</h3>\n<ul>\n<li><strong><code>data</code></strong> (array). Each item: <code>timestamp</code>, <code>userEmail</code>, <code>action</code> (<code>create</code>, <code>update</code>, <code>delete</code>, ...), <code>description</code>, <code>status</code>, <code>error</code>, <code>resources</code> (<code>type</code>, <code>id</code>, <code>name</code>) and <code>request</code> (the request body).</li>\n<li><strong><code>pagination</code></strong>: <code>nextCursor</code>.</li>\n</ul>\n<p>Requires the workspace admin or owner role.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}},"urlObject":{"protocol":"https","path":["platform","api","v1","workspaces","{{workspace_id}}","workspace-logs"],"host":["platform","pvml","com"],"query":[{"key":"pageSize","value":"50"},{"disabled":true,"key":"action","value":"{\"operator\":\"eq\",\"value\":\"update\"}"}],"variable":[]}},"response":[{"id":"295fdd28-e73c-4e76-958d-8bcc2f06fe43","name":"Get Activity Log - 200 OK","originalRequest":{"method":"GET","header":[],"url":{"raw":"https://platform.pvml.com/platform/api/v1/workspaces/{{workspace_id}}/workspace-logs?pageSize=50","protocol":"https","host":["platform","pvml","com"],"path":["platform","api","v1","workspaces","{{workspace_id}}","workspace-logs"],"query":[{"key":"pageSize","value":"50"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"data\": [\n        {\n            \"timestamp\": \"2026-10-30T01:05:57.418Z\",\n            \"id\": \"6f0c06407a6a08673c3c8207\",\n            \"workspaceId\": \"55613db2-386b-5efd-8caa-8128f496c24a\",\n            \"userId\": \"bd095617-299a-5bb2-948d-0066747a23d3\",\n            \"userEmail\": \"jane.doe@example.com\",\n            \"traceId\": \"1183329127485993150\",\n            \"action\": \"update\",\n            \"description\": \"pull datasource relations from source\",\n            \"status\": \"success\",\n            \"resources\": [\n                {\n                    \"type\": \"datasource\",\n                    \"id\": \"32fef0f0-33bb-5bbe-82fa-0a2e4439666c\",\n                    \"name\": \"MyDB\"\n                }\n            ]\n        },\n        {\n            \"timestamp\": \"2026-10-30T01:05:51.473Z\",\n            \"id\": \"6f0c54860ac18a7e35bb742e\",\n            \"workspaceId\": \"55613db2-386b-5efd-8caa-8128f496c24a\",\n            \"userId\": \"bd095617-299a-5bb2-948d-0066747a23d3\",\n            \"userEmail\": \"jane.doe@example.com\",\n            \"traceId\": \"2812779069585134587\",\n            \"action\": \"update\",\n            \"description\": \"update group virtual databases\",\n            \"status\": \"success\",\n            \"resources\": [\n                {\n                    \"type\": \"group\",\n                    \"id\": \"1845013f-57ed-51da-bddf-16fc89deab9d\",\n                    \"name\": \"branch managers\"\n                }\n            ],\n            \"request\": {\n                \"vdbsToAdd\": [\n                    \"25b1a339-9c66-5e4a-9ec1-dc9a7fe0bd69\"\n                ],\n                \"vdbsToRemove\": []\n            }\n        }\n    ],\n    \"pagination\": {\n        \"nextCursor\": \"eyJwYWdlIjoyfQ\"\n    }\n}"}],"_postman_id":"fb2df94d-65b0-4cf9-8cd3-2a9f5a89a15c"}],"id":"e0ec78de-9933-44b6-b553-d3f577c4100b","description":"<p>PVML keeps two audit logs. In the platform they are two pages:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Platform</th>\n<th>API</th>\n<th>What it records</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Data Access Audit</strong></td>\n<td><strong>Get Audit Records</strong>, <strong>Get Audit Record</strong></td>\n<td>Every query on a VDB: who ran it, on which VDB, the SQL, the source (BI tool, notebook or AI client), the status, the duration, and the user's question when it came from an AI client.</td>\n</tr>\n<tr>\n<td><strong>System Audit</strong></td>\n<td><strong>Get Activity Log</strong></td>\n<td>Workspace-level actions: who created, changed or deleted a policy, a VDB, a datasource, a user or a connection, and when.</td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Example:</strong> a compliance officer asks \"who looked at customer data last month, and who gave them access?\"</p>\n<ol>\n<li><strong>Data Access Audit</strong> shows each query on the customer VDB: the user, the time and whether it succeeded or was blocked.</li>\n<li><strong>System Audit</strong> shows who added that user to the VDB, and when.</li>\n</ol>\n","_postman_id":"e0ec78de-9933-44b6-b553-d3f577c4100b","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":true,"source":{"_postman_id":"230ce1f4-4c29-4006-87b5-45620ae91c61","id":"230ce1f4-4c29-4006-87b5-45620ae91c61","name":"PVML Documentation","type":"collection"}}}],"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]}},"event":[{"listen":"prerequest","script":{"exec":[""],"id":"6ada1329-8184-4ed6-a92d-2d25ec417c25","type":"text/javascript"}},{"listen":"test","script":{"exec":[""],"id":"f8ab2d54-11e4-43ef-8b16-a4c77b732c15","type":"text/javascript"}}],"variable":[{"key":"baseUrl","value":"https://platform.pvml.com"},{"key":"json_web_token_105i","secret":true}]}