{"id":9096,"date":"2026-09-19T05:00:54","date_gmt":"2026-09-19T05:00:54","guid":{"rendered":"https:\/\/www.coffee.ai\/articles\/best-salesforce-data-warehouse-practices"},"modified":"2026-09-19T05:00:54","modified_gmt":"2026-09-19T05:00:54","slug":"best-salesforce-data-warehouse-practices","status":"publish","type":"post","link":"https:\/\/www.coffee.ai\/articles\/best-salesforce-data-warehouse-practices","title":{"rendered":"Salesforce Data Warehouse Best Practices: A Playbook"},"content":{"rendered":"<p><em>Written by: Doug Camplejohn, CEO &amp; Co-Founder, Coffee<\/em><\/p>\n<h2 id=\"key-takeaways\">Key Takeaways For Salesforce Warehouse Design<\/h2>\n<ul>\n<li>Treat Salesforce extraction as an engineering discipline and separate operational data from warehouse analytics.<\/li>\n<li>Avoid mirroring Salesforce\u2019s transactional object model in the warehouse.<\/li>\n<li>Build an immutable raw or bronze layer first and preserve every Salesforce system field.<\/li>\n<li>Design a star schema with explicit grain definitions so core reports stay reliable.<\/li>\n<li>Extract OpportunityHistory with current-state records to support forecasting and pipeline analysis.<\/li>\n<li>Handle deletes with QueryAll and soft-delete logic so historical facts remain accurate.<\/li>\n<li>Use surrogate keys to prevent multi-org ID collisions and keep fact tables stable.<\/li>\n<li>Respect Salesforce API limits and monitor key limit metrics during extraction.<\/li>\n<li>Set freshness SLAs and add observability to catch row-count anomalies, schema drift, and SystemModstamp lag early.<\/li>\n<li>Coffee improves upstream Salesforce data quality so the warehouse receives accurate, complete records.<\/li>\n<\/ul>\n<p><a href=\"https:\/\/www.coffee.ai\/pricing\" class=\"solid-button\" target=\"_blank\">See Coffee\u2019s Salesforce Data Quality Plans<\/a><\/p>\n<h2>1. Choose The Right Salesforce Extraction Architecture<\/h2>\n<p>SystemModstamp is the default incremental watermark for most objects because it captures both user and automation changes. When you need near-real-time event streaming, Change Data Capture fits better than scheduled pulls. For reliable scheduled extraction with delete detection, use the Salesforce REST API Get Updated resource and pair it with a separate QueryAll pass. Full loads make sense only for small, slowly changing reference objects.<\/p>\n<p>The Get Updated resource uses <code>SystemModstamp<\/code> to determine an object\u2019s add or change date and falls back to <code>LastModifiedDate<\/code> if <code>SystemModstamp<\/code> is unavailable. SystemModstamp captures automation-driven updates, such as triggers and workflow rules, that <code>LastModifiedDate<\/code> does not, so it provides broader coverage. The Get Updated resource caps its lookback at 30 days and returns a maximum of 600,000 IDs per call. When you exceed that limit, it returns an <code>EXCEEDED_ID_LIMIT<\/code> error, which you fix by narrowing the start-to-end window.<\/p>\n<p>The three primary extraction APIs and their trade-offs are compared in the table below.<\/p>\n<table>\n<thead>\n<tr>\n<th>Extraction Method<\/th>\n<th>Best For<\/th>\n<th>Delete Detection<\/th>\n<th>API Limit Impact<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>REST API Get Updated<\/td>\n<td>Scheduled incremental extraction<\/td>\n<td>Yes, via QueryAll<\/td>\n<td>Counts against DailyApiRequests<\/td>\n<\/tr>\n<tr>\n<td>Change Data Capture<\/td>\n<td>Near-real-time event streaming<\/td>\n<td>Yes, via event stream<\/td>\n<td>Counts against DailyDeliveredPlatformEvents<\/td>\n<\/tr>\n<tr>\n<td>Bulk API 2.0<\/td>\n<td>Large-volume full or incremental loads<\/td>\n<td>No, requires separate QueryAll<\/td>\n<td>Counts against DailyBulkApiBatches and DailyBulkV2QueryJobs<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>A watermark-based incremental extraction pattern in SQL looks like this:<\/p>\n<pre><code>-- Incremental extraction using SystemModstamp watermark SELECT Id, Name, SystemModstamp, LastModifiedDate, IsDeleted FROM salesforce_raw.opportunity WHERE SystemModstamp &gt; ( SELECT MAX(last_extracted_at) FROM pipeline_state WHERE object = 'Opportunity' ) ORDER BY SystemModstamp ASC; <\/code><\/pre>\n<h2>2. Build A Raw Or Bronze Immutable Layer First<\/h2>\n<p>An immutable raw layer gives you a reliable audit trail and a safe place to rebuild downstream models. When transformation logic changes, you can replay from raw instead of re-querying Salesforce.<\/p>\n<p>Every Salesforce system field must survive extraction intact. The fields that must be preserved are:<\/p>\n<ul>\n<li><code>Id<\/code> \u2014 the Salesforce record identifier<\/li>\n<li><code>CreatedDate<\/code> \u2014 when the record was first created<\/li>\n<li><code>LastModifiedDate<\/code> \u2014 when a user last modified the record<\/li>\n<li><code>SystemModstamp<\/code> \u2014 when a user or automated process last modified the record<\/li>\n<li><code>IsDeleted<\/code> \u2014 whether the record has been soft-deleted<\/li>\n<\/ul>\n<p><code>SystemModstamp<\/code> and <code>LastModifiedDate<\/code> can diverge. A trigger firing after a user save updates <code>SystemModstamp<\/code> but not <code>LastModifiedDate<\/code>. If you drop <code>SystemModstamp<\/code> from the raw layer, you lose the ability to detect those automation-driven changes in future incremental runs.<\/p>\n<p>The recommended layer architecture is:<\/p>\n<pre><code>Salesforce (source) \u2502 \u25bc Raw \/ Bronze Layer \u2190 immutable, untransformed, all system fields preserved \u2502 \u25bc Staging Layer \u2190 deduplication, type casting, soft-delete handling \u2502 \u25bc Star Schema Marts \u2190 fact_opportunity, dim_account, dim_contact, etc. <\/code><\/pre>\n<h2>3. Map Salesforce Objects To A Dimensional Model<\/h2>\n<p>Mirroring Salesforce\u2019s transactional schema in the warehouse creates slow, fragile analytics. <a href=\"https:\/\/pmsynapse.in\/blog\/star-schema-fact-dimension-analytics-model\" target=\"_blank\" rel=\"noindex nofollow\">Salesforce\u2019s object model is normalized for transactional integrity<\/a> and favors row-level writes, not analytical query performance.<\/p>\n<p>In Salesforce Sales Cloud, a closed deal\u2019s data lives across <code>Account<\/code>, <code>Contact<\/code>, <code>Opportunity<\/code>, and <code>Opportunity Contact Role<\/code>, as shown in Salesforce\u2019s official Sales Cloud data model. Each object stores a different aspect of the deal and joins through foreign keys tuned for transactions. That structure works for operational workflows but becomes awkward for reporting.<\/p>\n<p>The correct mapping is:<\/p>\n<ul>\n<li>Salesforce <code>Account<\/code> \u2192 <code>dim_account<\/code> (SCD Type 2, one row per version)<\/li>\n<li>Salesforce <code>Contact<\/code> \u2192 <code>dim_contact<\/code> (SCD Type 2)<\/li>\n<li>Salesforce <code>Opportunity<\/code> \u2192 <code>fact_opportunity<\/code> (grain defined explicitly, see Section 4)<\/li>\n<\/ul>\n<p>Copying the operational schema into the warehouse is a known dimensional modeling anti-pattern. ERP and CRM schemas support transactions, while analytical schemas support reporting and forecasting. Exposing the operational model directly produces complex SQL and queries that business users struggle to trust.<\/p>\n<h2>4. Design A Star Schema With Explicit Grain Definitions<\/h2>\n<p>Define what one row in each fact table represents before you write any SQL. For <code>fact_opportunity<\/code>, one row should represent one opportunity at a specific point in its lifecycle.<\/p>\n<p>A practical test is to complete the sentence \u201cone row in this fact table represents\u2026\u201d and stop if the answer is vague. Explicit grain definitions for core Salesforce-derived objects are:<\/p>\n<ul>\n<li><code>fact_opportunity<\/code>: one row per opportunity per snapshot date (periodic snapshot) or one row per opportunity stage change (transactional). Use separate tables for each grain.<\/li>\n<li><code>dim_account<\/code>: one row per account per version (SCD Type 2) with <code>effective_date<\/code> and <code>expiry_date<\/code> columns<\/li>\n<li><code>dim_contact<\/code>: one row per contact per version (SCD Type 2)<\/li>\n<\/ul>\n<p>A sample fact table DDL with grain declaration:<\/p>\n<pre><code>-- GRAIN: one row per opportunity per weekly snapshot date -- SOURCE: Salesforce Opportunity (current state) + OpportunityHistory CREATE TABLE mart.fact_opportunity_snapshot ( snapshot_date DATE NOT NULL, opportunity_key INTEGER NOT NULL, -- surrogate key account_key INTEGER NOT NULL, -- FK to dim_account owner_key INTEGER NOT NULL, -- FK to dim_user stage_name VARCHAR(80), amount NUMERIC(18,2), close_date DATE, is_closed BOOLEAN, is_won BOOLEAN, created_date TIMESTAMP, last_modified_date TIMESTAMP, PRIMARY KEY (snapshot_date, opportunity_key) ); <\/code><\/pre>\n<p>Mixing transaction-level and period-level facts in one table produces aggregation errors that usually require a full fact table rebuild.<\/p>\n<h2>5. Preserve Salesforce Opportunity History For Forecasting<\/h2>\n<p>OpportunityHistory is essential for forecasting because it records how deals move through stages over time. Extract <code>OpportunityHistory<\/code> alongside current-state <code>Opportunity<\/code> records so you can reconstruct pipeline velocity, stage duration, and historical conversion rates.<\/p>\n<p>The <code>OpportunityHistory<\/code> fields required for forecasting analytics are:<\/p>\n<ul>\n<li><code>OpportunityId<\/code> \u2014 links back to the parent Opportunity<\/li>\n<li><code>StageName<\/code> \u2014 the stage at the time of the history record<\/li>\n<li><code>Amount<\/code> \u2014 the deal value at that point in time<\/li>\n<li><code>CloseDate<\/code> \u2014 the projected close date at that point in time<\/li>\n<li><code>CreatedDate<\/code> \u2014 when this history record was written<\/li>\n<li><code>CreatedById<\/code> \u2014 which user or process triggered the change<\/li>\n<\/ul>\n<p>Daily snapshots of open pipeline, with one row per open opportunity per day, unlock trend analysis. You can track pipeline coverage over time, stage duration, and slip rates. Without <code>OpportunityHistory<\/code>, questions about how long deals spent in a given stage last quarter remain unanswerable from the warehouse.<\/p>\n<h2>6. Handle Deleted Salesforce Records Safely In The Warehouse<\/h2>\n<p>QueryAll is required for delete-aware extraction because it returns soft-deleted records. Use QueryAll instead of Query, filter on <code>IsDeleted<\/code>, and implement a soft-delete strategy in the warehouse. Your merge logic must handle <code>IsDeleted<\/code> flipping from <code>true<\/code> to <code>false<\/code> when records are undeleted.<\/p>\n<p>The standard <code>Query<\/code> resource automatically filters out deleted records. <code>QueryAll<\/code> includes them, and the <code>IsDeleted<\/code> field distinguishes deleted from active records in the result set. Salesforce allows records to be undeleted from the Recycle Bin for up to 15 days after deletion. Records may be purged earlier if the Recycle Bin is emptied or its storage limit is reached, and Salesforce does not guarantee the exact time of permanent deletion. That behavior means a record your warehouse marked as deleted can reappear as active. A merge statement that only handles the <code>true<\/code> direction will silently leave a deleted record in the warehouse after an undelete event.<\/p>\n<p>A merge statement that handles both directions of <code>IsDeleted<\/code>:<\/p>\n<pre><code>MERGE INTO mart.dim_account AS target USING staging.account_delta AS source ON target.salesforce_id = source.Id WHEN MATCHED AND source.IsDeleted = TRUE THEN UPDATE SET target.is_deleted = TRUE, target.deleted_at = source.SystemModstamp, target.expiry_date = CURRENT_DATE WHEN MATCHED AND source.IsDeleted = FALSE THEN UPDATE SET target.is_deleted = FALSE, target.deleted_at = NULL, target.expiry_date = '9999-12-31' WHEN NOT MATCHED THEN INSERT (salesforce_id, account_name, is_deleted, effective_date, expiry_date) VALUES (source.Id, source.Name, source.IsDeleted, CURRENT_DATE, '9999-12-31'); <\/code><\/pre>\n<p><a href=\"https:\/\/www.coffee.ai\/pricing\" class=\"solid-button\" target=\"_blank\">Check Coffee\u2019s Extraction-Ready CRM Setup<\/a><\/p>\n<h2>7. Solve Multi-Org ID Collisions With Surrogate Keys<\/h2>\n<p>Salesforce record IDs are unique within a single org, but <a href=\"https:\/\/salesforce.stackexchange.com\/questions\/438151\/are-salesforce-ids-globally-unique\" target=\"_blank\" rel=\"noindex nofollow\">sandboxes cloned from production can share the same record IDs<\/a> as the source org. Multi-org environments need surrogate keys in the warehouse and a mapping table to keep joins stable.<\/p>\n<p>The surrogate key strategy for multi-org environments:<\/p>\n<ul>\n<li>Generate a surrogate key as a hash of <code>org_id || salesforce_id<\/code> or use a sequence-generated integer with a mapping table that stores <code>(org_id, salesforce_id, surrogate_key)<\/code>.<\/li>\n<li>Maintain the mapping table as the authoritative join path between Salesforce IDs and warehouse keys.<\/li>\n<li>Resolve the same Account or Contact appearing in multiple orgs at the warehouse layer through identity resolution, matching on normalized email domain, company name, or other stable identifiers.<\/li>\n<\/ul>\n<p>Surrogate keys stay immutable, so source system changes do not ripple through fact tables. They also support SCD Type 2 handling when a natural key points to multiple versions of the same entity over time.<\/p>\n<h2>8. Manage Salesforce API Limits During Extraction<\/h2>\n<p>Extraction jobs compete with every other integration in the org, so schedule them off-peak and monitor <code>DailyApiRequests<\/code> and <code>DailyBulkApiBatches<\/code> through the Salesforce REST API Limits resource. That resource reports both maximum and remaining allocation, accurate within five minutes, which gives you enough warning to throttle before a job fails. Filter sensitive fields at extraction so restricted data never lands in the raw layer.<\/p>\n<p>Key limit interactions to track:<\/p>\n<ul>\n<li><code>DailyApiRequests<\/code> is a shared constraint across all org integrations, so warehouse extraction jobs share the same pool as every other REST API call.<\/li>\n<li>Bulk API and Bulk API 2.0 share the <code>DailyBulkApiBatches<\/code> allocation pool, so heavy extract workloads can exhaust the budget for other bulk operations.<\/li>\n<li>Bulk API 2.0 query jobs are constrained by <code>DailyBulkV2QueryJobs<\/code> and <code>DailyBulkV2QueryFileStorageMB<\/code>.<\/li>\n<li>Change Data Capture via CometD or Pub\/Sub API counts against <code>DailyDeliveredPlatformEvents<\/code>, which does not apply to Apex triggers or flows.<\/li>\n<\/ul>\n<p>Field-level security is a silent failure mode. If the integration user lacks field-level access to <code>SystemModstamp<\/code>, explicit field requests return a 400 error. In relationship traversal responses, the field is simply absent. Verify field-level access for all system fields, including <code>SystemModstamp<\/code>, <code>IsDeleted<\/code>, and <code>CreatedDate<\/code>, before go-live and after any Salesforce permission set change.<\/p>\n<h2>9. Use Salesforce Data 360 And Your Warehouse Together<\/h2>\n<p>Data 360 focuses on activation and real-time customer experiences, while an external warehouse focuses on deep analysis and cross-functional reporting. Most mature teams combine both platforms so insights flow into activation.<\/p>\n<p>Data 360\u2019s Zero Copy Data Federation lets Data 360 query data in Snowflake, Databricks, or BigQuery at read time without copying it into the Data 360 lake. Supported capabilities with zero-copy federation include identity resolution, segmentation, activation, batch data transforms, and batch Calculated Insights. Capabilities not supported with zero-copy include near real-time identity resolution, streaming data transforms, rapid segmentation and activation, and schema changes such as field renames in the source table.<\/p>\n<p>For a deeper platform comparison, see <em>Salesforce Data Cloud Vs Snowflake: When To Use Both<\/em> and <em>CRM Data Warehouse Architecture: A Practical Blueprint<\/em> for the broader architectural pattern.<\/p>\n<h2>10. Set Freshness SLAs And Add Pipeline Observability<\/h2>\n<p>Freshness expectations should be explicit before go-live so teams know when data is considered late. A green dbt run only confirms that the SQL executed without error. A star schema reaches production readiness when the team can detect, explain, and contain drift in production.<\/p>\n<p>Silent failure detection requires active monitoring across three dimensions:<\/p>\n<ul>\n<li><strong>Row count anomalies:<\/strong> compare source row counts to warehouse row counts after every extraction run. The fact table should never exceed the source count for any given object and time window.<\/li>\n<li><strong>Schema drift:<\/strong> alert when a Salesforce field is renamed, removed, or recast. Without alerts, downstream transformation logic keeps running on wrong assumptions until someone notices a broken dashboard.<\/li>\n<li><strong>SystemModstamp lag:<\/strong> monitor the gap between <code>MAX(SystemModstamp)<\/code> in the raw layer and the current timestamp. A widening gap indicates the extraction job is silently failing or falling behind.<\/li>\n<\/ul>\n<p>Unexpected null rates in key fields, such as <code>Amount<\/code> on open opportunities, <code>CloseDate<\/code> on pipeline records, or <code>AccountId<\/code> on contacts, signal that source data quality has degraded upstream of the warehouse. These checks belong in the pipeline as continuous monitors rather than in a quarterly data audit.<\/p>\n<h2>Why Salesforce Source Data Quality Still Limits The Pipeline<\/h2>\n<p>Every practice in this playbook assumes the data entering Salesforce is worth extracting. <a href=\"https:\/\/pixelwand.io\/blog\/crm-data-quality-guide\" target=\"_blank\" rel=\"noindex nofollow\">Seventy-six percent of organizations report that less than half of their CRM data is accurate and complete<\/a>. <a href=\"https:\/\/datamagnet.co\/post\/duplicate-record-detection-matching-algorithms-explained\" target=\"_blank\" rel=\"noindex nofollow\">A 2021 Plauti analysis of more than 12 billion Salesforce records found that 45% of all new records entered that year were duplicates<\/a>. A warehouse pipeline built on that source data inherits every duplicate, missing field, and stale record, and downstream transformation logic cannot repair problems that originate upstream.<\/p>\n<p>Coffee\u2019s Companion App for Salesforce addresses the data-in problem directly. The Coffee Agent auto-creates contacts and companies from emails and calendar events, enriches records with job titles and firmographic data, logs activities automatically, and captures meeting notes and summaries. As a result, the Salesforce records the warehouse extracts reflect what actually happened in the sales process. Coffee also operates as a Standalone AI-First CRM for teams not on Salesforce. Both deployments are SOC 2 Type 2 and GDPR compliant, and data is not used to train public models.<\/p>\n<p>Good data out of the warehouse requires good data into Salesforce. The extraction architecture, schema design, and observability practices in this playbook handle the analytical layer. Coffee strengthens the source layer so analytics teams can trust their inputs.<\/p>\n<p><a href=\"https:\/\/www.coffee.ai\/pricing\" class=\"solid-button\" target=\"_blank\">Explore Coffee For Salesforce And AI CRM<\/a><\/p>\n<h2>Frequently Asked Questions<\/h2>\n<h3>What Is The Difference Between SystemModstamp And LastModifiedDate For Salesforce Extraction?<\/h3>\n<p>LastModifiedDate reflects the date and time a record was last modified by a human user. SystemModstamp reflects the date and time a record was last modified by a user or by an automated process, such as a trigger, workflow rule, or process builder action. As covered in Section 1, SystemModstamp is the broader-coverage watermark because it captures automation-driven updates.<\/p>\n<h3>Why Should You Avoid Mirroring Salesforce\u2019s Object Model In The Warehouse?<\/h3>\n<p><a href=\"https:\/\/pmsynapse.in\/blog\/star-schema-fact-dimension-analytics-model\" target=\"_blank\" rel=\"noindex nofollow\">Salesforce\u2019s schema is normalized for transactional integrity<\/a> and spreads one business fact across many joined tables. Section 3 covers why this structure fails for analytics. The short version: analytical queries become slow and complex when they must traverse many normalized tables for each question.<\/p>\n<h3>How Do You Prevent Deleted Salesforce Records From Corrupting Warehouse Data?<\/h3>\n<p>As Section 6 explains, QueryAll is required because the standard Query resource filters out deleted records. The warehouse soft-delete strategy described there also handles undeletes. The extra detail to remember is that the Recycle Bin has a storage cap of 25 times the org\u2019s data storage, and in Salesforce Classic the retention can extend to 30 days, so purge timing varies by org usage.<\/p>\n<h3>What Is The Correct Grain For A Salesforce Opportunity Fact Table?<\/h3>\n<p>The correct grain depends on the analytical use case and must be declared explicitly. Section 4 outlines two common grains: one row per opportunity per snapshot date for trend and forecasting analysis, and one row per opportunity per stage change for transition analysis. Keeping these grains in separate fact tables avoids aggregation errors.<\/p>\n<h3>When Should A Team Use Salesforce Data 360 Instead Of An External Warehouse?<\/h3>\n<p>Data 360 and an external warehouse serve different jobs, and most mature teams use both. Data 360 is optimized for real-time customer activation, such as suppressing a promotional email when a service case opens or refreshing audience segments frequently. An external warehouse like Snowflake, BigQuery, or Databricks is optimized for deep analytical queries, cross-functional reporting, and long-term historical analysis, and supports AI and ML feature engineering. Data 360\u2019s Zero Copy Federation lets it query data left in an external warehouse, but that federation does not support near real-time identity resolution, streaming data transforms, or rapid segmentation and activation. The practical split is to use Data 360 for activation and real-time personalization and use the external warehouse for analysis and forecasting.<\/p>\n<section data-read-next=\"true\">\n<h2>Read Next<\/h2>\n<ul>\n<li><a href=\"https:\/\/coffee.ai\/articles\/salesforce-data-migration-best-practices\" target=\"_blank\">Salesforce Data Migration Best Practices: An 8-Step Playbook<\/a><\/li>\n<li><a href=\"https:\/\/coffee.ai\/articles\/crm-data-warehouse-architecture\" target=\"_blank\">CRM Data Warehouse Architecture: A Practical Blueprint<\/a><\/li>\n<li><a href=\"https:\/\/coffee.ai\/articles\/salesforce-migration-automation-2026\" target=\"_blank\">Salesforce Migration Automation: Tools &amp; Best Practices<\/a><\/li>\n<li><a href=\"https:\/\/coffee.ai\/articles\/enterprise-salesforce-integration-best-practices\" target=\"_blank\">Salesforce Integration Best Practices for Large Enterprises<\/a><\/li>\n<li><a href=\"https:\/\/coffee.ai\/articles\/salesforce-migration-best-practices-2026\" target=\"_blank\">Salesforce Migration Best Practices: 12-Step Checklist<\/a><\/li>\n<\/ul>\n<\/section>\n","protected":false},"excerpt":{"rendered":"<p>Master Salesforce data warehouse architecture with Coffee&#8217;s expert playbook. Build reliable pipelines, star schemas, and SLAs. Start optimizing today.<\/p>\n","protected":false},"author":11,"featured_media":9095,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-9096","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.coffee.ai\/articles\/wp-json\/wp\/v2\/posts\/9096","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.coffee.ai\/articles\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.coffee.ai\/articles\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/www.coffee.ai\/articles\/wp-json\/wp\/v2\/comments?post=9096"}],"version-history":[{"count":0,"href":"https:\/\/www.coffee.ai\/articles\/wp-json\/wp\/v2\/posts\/9096\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.coffee.ai\/articles\/wp-json\/wp\/v2\/media\/9095"}],"wp:attachment":[{"href":"https:\/\/www.coffee.ai\/articles\/wp-json\/wp\/v2\/media?parent=9096"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.coffee.ai\/articles\/wp-json\/wp\/v2\/categories?post=9096"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.coffee.ai\/articles\/wp-json\/wp\/v2\/tags?post=9096"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}