From e5f2290db968b3f68b489806ca49dbdc8cacfd7c Mon Sep 17 00:00:00 2001 From: Karl Waldman Date: Thu, 30 Jul 2026 09:51:04 -0400 Subject: [PATCH 1/2] docs: capture Marketplace launch playbook --- DEPLOYMENT_GUIDE.md | 19 ++- GOOGLE_MARKETPLACE_PLAYBOOK.md | 264 +++++++++++++++++++++++++++++++++ MARKETPLACE_LISTING.md | 53 ++++--- OAUTH_VERIFICATION.md | 68 +++++---- PORTFOLIO.md | 9 ++ README.md | 14 +- YOUTUBE_PROMOTION.md | 205 +++++++++++++++++++++++++ test/public-claims.test.js | 20 ++- 8 files changed, 578 insertions(+), 74 deletions(-) create mode 100644 GOOGLE_MARKETPLACE_PLAYBOOK.md create mode 100644 YOUTUBE_PROMOTION.md diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md index ce34ac7..fcfea71 100644 --- a/DEPLOYMENT_GUIDE.md +++ b/DEPLOYMENT_GUIDE.md @@ -6,20 +6,19 @@ test installation, screenshots, and submission require the publisher account. ## Current release gate -- Runtime version: `1.2.1` +- Runtime version: `1.2.2` - Production Apps Script ID: `1rlVWvciYu-wzqnY009I3oW-08ZPazYK1snrrMg9NNY7c5WBSkUK8W2Hb` -- Current immutable Apps Script version: `10` +- Current immutable Apps Script version: `11` - GitHub production release workflow: `https://github.com/OilpriceAPI/google-sheets-addin/actions/workflows/apps-script-release.yml` -- Marketplace status: version 9 draft resubmitted July 29, 2026 and locked in - Google review; update it to version 10 when App Configuration is editable +- Marketplace status: version 9 submission rejected/paused pending OAuth + approval; after approval update it to version 11 and resubmit once - Runtime push/version and local deployment checks: complete - Marketplace review receipt and a real 1280x800 screenshot: complete - Public homepage, privacy policy, and terms deployment: complete -- Separate OAuth verification dependency: confirm Search Console ownership, - record the end-to-end authorization demo, submit branding and data-access - verification, and preserve the confirmation receipt +- OAuth branding is verified; Data Access verification was submitted July 30, + 2026 with `https://youtu.be/FakNSmBddhE` and is under review - Canonical remaining-work issue: `https://github.com/OilpriceAPI/google-sheets-addin/issues/20` @@ -115,6 +114,10 @@ configuration, a formula result, and key deletion. The prepared justification text is in `MARKETPLACE_LISTING.md`; the exact branding values, scope justifications, and recording sequence are in `OAUTH_VERIFICATION.md`. +For the proven draft-install activation sequence, custom-function registration +diagnostics, reviewer-fixture rules, and submission notes, follow +`GOOGLE_MARKETPLACE_PLAYBOOK.md`. + ## 5. Install a test Editor add-on Create a blank spreadsheet containing no customer data. @@ -184,7 +187,7 @@ screenshots until they have been reviewed for secrets and customer data. After the smoke passes against the exact pushed source: ```bash -npm run deploy:version -- "OilPriceAPI for Google Sheets 1.2.1" +npm run deploy:version -- "OilPriceAPI for Google Sheets 1.2.2" npm run deploy:list ``` diff --git a/GOOGLE_MARKETPLACE_PLAYBOOK.md b/GOOGLE_MARKETPLACE_PLAYBOOK.md new file mode 100644 index 0000000..3626ff2 --- /dev/null +++ b/GOOGLE_MARKETPLACE_PLAYBOOK.md @@ -0,0 +1,264 @@ +# Google Sheets add-on launch playbook + +This playbook captures the production lessons from the original +`OilPriceAPI for Google Sheets™` submission so later OilPriceAPI add-ons can +reuse the proven path without repeating the same diagnostics. + +Use this together with: + +- `DEPLOYMENT_GUIDE.md` for the release commands and Google configuration; +- `MARKETPLACE_LISTING.md` for reviewed listing copy and assets; +- `OAUTH_VERIFICATION.md` for the current submission record; +- `PORTFOLIO.md` for product order and acquisition measurement. + +## Proven sequence + +Follow this order. Reordering the Google steps creates ambiguous failures and +avoidable review delays. + +1. Create a standalone Apps Script project and link it to a standard, + organization-controlled Google Cloud project. +2. Deploy the exact reviewed source, run automated validation, smoke it with a + non-customer key, and create an immutable Apps Script version. +3. Configure identical functional scopes in the Apps Script manifest, Google + Auth Platform Data Access page, and Workspace Marketplace SDK. +4. Create public app-specific homepage, privacy, terms, support, and opt-out + URLs. Verify the authorized domain through the same organization-controlled + account used for the Cloud project. +5. Configure OAuth as External and move it to In production. +6. Publish and verify OAuth branding before trying to prepare Data Access + verification. +7. Add the publisher or dedicated reviewer account as a Marketplace draft + tester. +8. Install the real Marketplace draft, select **Use in this document**, refresh + the spreadsheet, and run the installed-add-on smoke. +9. Record a clean OAuth review video from that installed draft. +10. Submit OAuth Data Access verification once and preserve the receipt. +11. Wait for OAuth approval before submitting or resubmitting the Marketplace + listing when Google has explicitly required that sequence. +12. Update the Marketplace integration to the exact tested Apps Script version, + submit once, and repeat the installed-add-on smoke after publication. + +## Scope contract + +Every OilPriceAPI Sheets add-on currently needs only these three functional +scopes: + +```text +https://www.googleapis.com/auth/spreadsheets.currentonly +https://www.googleapis.com/auth/script.external_request +https://www.googleapis.com/auth/script.container.ui +``` + +Google may display `userinfo.email` and `userinfo.profile` as identity defaults. +Do not add those defaults to product logic, and do not add Drive-wide access. + +Use this combined Data Access justification: + +> OilPriceAPI for Google Sheets uses spreadsheets.currentonly to read +> user-selected inputs and write formulas and requested market-data tables only +> in the spreadsheet where the add-on is open; it does not request Drive-wide +> access. It uses script.external_request only to send HTTPS GET requests to +> api.oilpriceapi.com for market data the user explicitly requests. Requests +> include the user's stored OilPriceAPI key and selected commodity identifiers +> or filters; general spreadsheet contents are not transferred. It uses +> script.container.ui to display the add-on menu, API-key sidebar, +> price-selection dialog, help alerts, diagnostics, and recovery actions inside +> Google Sheets. These are the narrowest scopes available: currentonly limits +> spreadsheet access to the active document, external_request is required to +> call the OilPriceAPI service, and container.ui is required for the in-sheet +> sidebar and dialogs. + +## The two Google test paths are not equivalent + +An Apps Script Editor add-on test deployment can prove menu, sidebar, dialog, +authorization, and API request behavior. It did not register the custom +function namespace in fresh test documents during this submission: + +- sidebar key save and Fetch Latest Available Prices worked; +- formula autocomplete was absent; +- even a service-free diagnostic custom function returned `#NAME?`; +- no formula execution appeared in Apps Script logs. + +That combination proves a host registration problem, not an OilPriceAPI +authentication failure. + +The real Marketplace draft install registered the functions correctly only +after this sequence: + +1. Add the Google account as a Marketplace draft tester. +2. Install the draft from its Marketplace page. +3. Open a new blank spreadsheet. +4. Select **Extensions → Add-ons → Manage add-ons**. +5. Open the OilPriceAPI add-on menu and select **Use in this document**. +6. Refresh the spreadsheet. + +After that, autocomplete appeared and `OILPRICE_PRICE`, `OILPRICE_CODES`, and +the other functions executed normally. + +Do not spend API debugging time on a `#NAME?` result when: + +- the sidebar fetch works; +- formula autocomplete is absent; and +- Apps Script shows no formula invocation. + +First confirm installed-add-on activation and namespace registration. + +## Credential-context lesson + +Sidebar handlers and custom functions can run in different Apps Script +authorization contexts. A key that works in the sidebar does not by itself +prove a formula can retrieve it. + +The production runtime therefore stores: + +- a primary copy in document properties; and +- a compatibility copy in the spreadsheet owner's user properties, keyed by + spreadsheet ID. + +The spreadsheet owner should configure the key. The OilPriceAPI account email +does not need to match the Google account email. The sidebar must expose only +configured/not-configured state; never return the stored credential to HTML, +cells, diagnostics, logs, screenshots, URLs, or issue trackers. + +Test all four credential states: + +1. no key; +2. valid key; +3. invalid or revoked key; +4. deleted key with diagnostic state cleared. + +## Avoid duplicate add-on contexts + +Simultaneous Apps Script test deployments and Marketplace draft installs can +produce duplicate extension entries and make results impossible to interpret. + +Before the final smoke: + +1. remove obsolete Apps Script test deployments; +2. uninstall stale draft installs if necessary; +3. confirm only one OilPriceAPI add-on is installed; +4. create a new spreadsheet; +5. enable the installed draft for that document; +6. refresh before testing formulas. + +Name test spreadsheets with the product, version, and path, for example: +`OPA Marketplace Draft v11 Smoke`. + +## Reviewer fixture + +Create one persistent synthetic reviewer user and API key per listing. + +- Use a clearly synthetic, non-customer email identity. +- Suppress lifecycle email, billing, and product analytics for the fixture. +- Give it only the datasets and conservative quota needed for review. +- Use a descriptive key name with the product and date. +- Verify one valid request and one invalid-key request before recording. +- Keep the key active through review, but never commit or post it. +- Deliver it only through Google's private reviewer field or existing review + email thread. + +Do not rotate or delete the fixture while a case is open unless exposure is +suspected. + +## Review video checklist + +The reviewer video and the public marketing video are different assets. +Keep the reviewer video Unlisted when it shows an account identifier or +unverified-app screen. + +Before recording: + +- enable Do Not Disturb; +- close unrelated tabs and applications; +- disable visible password-manager prompts; +- use a blank spreadsheet and synthetic key; +- set the OAuth consent language to English; +- record at 1080p or better; +- rehearse the exact flow once. + +Show: + +1. Marketplace installation and the complete OAuth consent flow; +2. the exact app name and every requested permission; +3. **Use in this document** activation; +4. the sidebar and masked/configured credential state; +5. successful connection and response-schema validation; +6. custom-function autocomplete and a live numeric result; +7. sidebar batch fetch writing into the active spreadsheet; +8. source, unit, timestamp, and freshness metadata. + +Before upload, sample the full rendered video at frequent intervals. Remove: + +- raw keys, passwords, cookies, or clipboard contents; +- browser autofill and password-manager overlays; +- notification panels; +- Terminal windows and unrelated tabs; +- macOS capture controls; +- customer or personal spreadsheet data. + +Upload the reviewer copy as Unlisted and verify the URL while signed out before +submitting it. + +## Google Auth submission notes + +- Branding must be published and verified before Data Access verification is + enabled. +- The Verification Center may describe unverified branding as “not being shown + to users” even when the underlying fields are complete. Open Branding, + publish it, then return to Verification Center. +- The Data Access form accepts one combined justification of up to 1,000 + characters and a reviewer-accessible YouTube URL. +- Confirm that Restricted scopes shows no rows. +- Save Data Access changes before returning to Verification Center. +- Submit once. Repeated submissions can trigger a cooldown and delay review. +- Preserve the exact confirmation text, date, screenshot, case ID, and review + email thread. + +For the original add-on, Google confirmed receipt on July 30, 2026, said the +first Trust and Safety email should arrive within 3–5 days, and warned that the +full review can take up to 4–6 weeks. + +## What can and cannot be automated + +Automate: + +- manifest and scope parity; +- packaging and secret scans; +- API success and negative-path tests; +- asset dimensions; +- legal/disclosure URL checks; +- immutable version records; +- API-client attribution and activation measurement. + +Keep a real-account smoke for: + +- Marketplace draft tester installation; +- **Use in this document** activation; +- custom-function registration and autocomplete; +- OAuth consent presentation; +- Google review submission and receipt capture. + +Google does not expose every Marketplace draft-install or custom-function +registration behavior through public Apps Script or Sheets APIs. API-written +formulas cannot bypass an unregistered custom-function namespace. + +## Per-add-on evidence packet + +Create a separate packet for every portfolio product: + +- Cloud project ID and numeric project number; +- Apps Script project ID and immutable version; +- Git commit and validation output; +- exact three-scope comparison; +- public homepage, privacy, terms, support, and opt-out URLs; +- dedicated reviewer fixture record; +- installed-draft smoke spreadsheet; +- real listing screenshots; +- unlisted OAuth review video; +- OAuth receipt and case thread; +- Marketplace receipt and published listing URL; +- post-publication smoke and log review. + +Do not reuse screenshots, detailed listing descriptions, reviewer keys, Cloud +projects, OAuth identities, or Apps Script projects across portfolio listings. diff --git a/MARKETPLACE_LISTING.md b/MARKETPLACE_LISTING.md index 2c80f57..d9af2bb 100644 --- a/MARKETPLACE_LISTING.md +++ b/MARKETPLACE_LISTING.md @@ -1,12 +1,12 @@ # Google Workspace Marketplace Listing -Status: the Marketplace draft was resubmitted July 29, 2026 and remains in -Google review. That locked draft references Apps Script version 9. The reviewed -1.2.1 runtime is published separately as immutable Apps Script version 10 and -is ready to replace version 9 when Google makes App Configuration editable. -OAuth branding and data-access verification have not yet been submitted. -Do not claim Marketplace availability until Google approves and publishes the -listing. +Status: the Marketplace draft was resubmitted July 29, 2026 and Google then +rejected/paused review pending OAuth approval. The submitted Marketplace +configuration references Apps Script version 9. The reviewed 1.2.2 runtime is +published separately as immutable Apps Script version 11 and must replace +version 9 after OAuth approval. OAuth branding is verified and Data Access +verification was submitted July 30. Do not resubmit Marketplace or claim +availability until Google approves OAuth and publishes the listing. ## App details @@ -77,35 +77,34 @@ identity defaults for product behavior and does not request Drive-wide access. - Google Cloud project: `oilpriceapi-sheets-addon` (`991152473434`) - Marketplace draft Apps Script version: `9` -- Latest reviewed immutable Apps Script version: `10` -- Runtime release represented by version 10: `1.2.1` +- Latest reviewed immutable Apps Script version: `11` +- Runtime release represented by version 11: `1.2.2` - Integration: Google Sheets Editor add-on - Install modes: individual and administrator - Regions: all regions -- Review state: **In review — resubmitted July 29, 2026** +- Review state: **Rejected/on hold pending OAuth approval** - Rejection email received: **July 27, 2026** -- Google Cloud receipt: **“The draft is in review and can't be edited.”** +- Marketplace instruction: **Do not resubmit until OAuth verification is + approved.** - Verification-page production deployment: `https://github.com/OilpriceAPI/website-clean/actions/runs/30434284989` -- OAuth submission state: **not submitted** +- OAuth branding state: **verified and shown to users** +- OAuth submission state: **submitted July 30, 2026 — under review** +- OAuth reviewer video: `https://youtu.be/FakNSmBddhE` - Canonical remaining-work issue: `https://github.com/OilpriceAPI/google-sheets-addin/issues/20` -The OAuth consent screen is **In production**. The manifest and prepared -submission use the three functional scopes above; the locked Marketplace draft -uses the same scopes. Google's default `userinfo.email` and -`userinfo.profile` scopes remain in place. The public homepage, privacy policy, -and terms were corrected and deployed from website PR 1461. Cache-busted -production checks returned HTTP 200 from the canonical domain and found the -expected disclosure text on all three pages. - -Google Auth Platform still reports branding and data access as unverified. -Before submission, an owner/editor of Cloud project `991152473434` must confirm -Search Console ownership for `oilpriceapi.com`, record the required continuous -OAuth demonstration, enter version 10 when Marketplace configuration becomes -editable, submit branding and data-access verification, and preserve the -resulting receipt. Track those actions only in issue 20 rather than opening -parallel submission issues. +The OAuth consent screen is **In production**. The manifest, submitted OAuth +configuration, and Marketplace configuration use the three functional scopes +above; Google's default `userinfo.email` and `userinfo.profile` scopes remain +in place. The public homepage, privacy policy, and terms were corrected and +deployed from website PR 1461. + +Google Trust and Safety confirmed receipt of the OAuth form on July 30. Keep +the reviewer video and reviewer key active and leave branding, URLs, and scopes +unchanged during review. After OAuth approval, update Marketplace from Apps +Script version 9 to version 11, resubmit once, and preserve the new receipt in +issue 20. ## Graphic assets diff --git a/OAUTH_VERIFICATION.md b/OAUTH_VERIFICATION.md index a24ce01..a9ca30a 100644 --- a/OAUTH_VERIFICATION.md +++ b/OAUTH_VERIFICATION.md @@ -4,19 +4,17 @@ This packet is for the production Google Cloud project `oilpriceapi-sheets-addon` (`991152473434`) and the original `OilPriceAPI for Google Sheets™` add-on. -## Status as of July 29, 2026 +## Status as of July 30, 2026 Completed release evidence: - Add-on PR 19 merged as `799f49f46059340ed332431f5c7ac87f5c91a695`. -- All 49 runtime, recovery, disclosure, deployment-package, asset, portfolio, - and secret-scan checks passed. +- All 54 runtime, recovery, disclosure, deployment-package, asset, portfolio, + and secret-scan checks passed for the current release. - The exact merged runtime was pushed to the production Apps Script project. -- Immutable Apps Script version 10 was created with description - `OilPriceAPI for Google Sheets 1.2.1 OAuth verification`. -- A fresh clone of version 10 matched the four-file reviewed release package - exactly. +- Immutable Apps Script version 11 was created for runtime `1.2.2`, merge + `3747fef2d09474c5b610bfa7154c7134a16e6a9f`. - Website PR 1461 merged as `c3acb510680992538315781fb0ce3dcec335bf20`. - Production deployment @@ -32,26 +30,29 @@ Completed release evidence: Current Google state: -- The Marketplace version 9 draft is in review and cannot currently be edited. -- Apps Script version 10 is prepared but is not yet selected in the locked - Marketplace App Configuration. +- Google rejected/paused the Marketplace version 9 submission pending OAuth + approval and instructed the publisher not to resubmit Marketplace yet. +- Apps Script version 11 is the prepared release and must replace version 9 in + Marketplace App Configuration after OAuth approval. - OAuth publishing status is **In production**. -- OAuth branding is **not verified**. -- OAuth data access is **not verified**. -- OAuth verification has **not been submitted**. -- No public OAuth demonstration URL or Google submission receipt exists yet. +- OAuth branding is **verified and shown to users**. +- OAuth data access was **submitted July 30, 2026 and is under review**. +- Google reports that Homepage requirements are under review. +- Reviewer video: `https://youtu.be/FakNSmBddhE`. +- Google confirmed that Trust and Safety received the form, expects the first + email within 3–5 days, and says full review can take up to 4–6 weeks. +- Submission receipt: + `https://github.com/OilpriceAPI/google-sheets-addin/issues/20#issuecomment-5131610169`. Remaining owner-session work: -1. Confirm that a Cloud project owner/editor is a verified Search Console owner - for `oilpriceapi.com`. -2. Record and publish the continuous end-to-end OAuth demonstration below. -3. Update Marketplace App Configuration to Apps Script version 10 when Google - makes the reviewed draft editable. -4. Submit OAuth branding and data-access verification with the exact scopes, - justifications, and public video URL. -5. Capture the confirmation text, date, case/reference ID if present, and - redacted screenshots in issue 20: +1. Monitor the project contact email and answer Trust and Safety in the same + case. +2. Keep the reviewer video, reviewer key, branding, URLs, and scope list stable + during review. +3. After OAuth approval, update Marketplace App Configuration from version 9 + to version 11 and resubmit once. +4. Preserve OAuth approval and Marketplace resubmission receipts in issue 20: `https://github.com/OilpriceAPI/google-sheets-addin/issues/20`. ## Branding values @@ -117,8 +118,14 @@ and a non-customer test spreadsheet and OilPriceAPI key. 11. Choose Delete API Key and show that the stored-key and diagnostic states are cleared. -The recording must not expose an API key, Google account identifier, customer -data, browser password manager, clipboard contents, or unrelated tabs. +The recording must not expose an API key, customer data, browser password +manager, clipboard contents, or unrelated tabs. Use a dedicated demo Google +account because its identifier can appear as part of the required consent +screen, and keep that reviewer copy Unlisted. + +The completed reviewer video is `https://youtu.be/FakNSmBddhE`. Keep it +Unlisted. Create a different sanitized public acquisition video using +`YOUTUBE_PROMOTION.md`. ## Submission order @@ -127,14 +134,13 @@ data, browser password manager, clipboard contents, or unrelated tabs. another domain. 3. Confirm Search Console ownership for `oilpriceapi.com`. 4. Push and smoke the exact reviewed Apps Script source. -5. Create a new immutable Apps Script version and enter that version in the - Marketplace SDK. +5. Create a new immutable Apps Script version. 6. Record and upload the demo video with link visibility enabled for the Google review team. -7. In Google Auth Platform, verify branding first, then submit Data Access - verification with the scope justifications and demo link. -8. Keep the Workspace Marketplace listing in review only after the OAuth - verification request is accepted for review. +7. In Google Auth Platform, verify and publish branding first, then submit Data + Access verification with the scope justifications and demo link. +8. When Google requires OAuth approval first, wait for it before entering the + new Apps Script version and resubmitting Marketplace. Do not claim that the add-on is publicly installable until Google approves and publishes the Marketplace listing. diff --git a/PORTFOLIO.md b/PORTFOLIO.md index e2a73d4..73e89c5 100644 --- a/PORTFOLIO.md +++ b/PORTFOLIO.md @@ -29,6 +29,8 @@ Every release package must pass `npm run validate`. Editor add-on smoke test and its OAuth configuration is In production. - The five listings must not reuse screenshots or detailed descriptions. Shared infrastructure is acceptable; duplicate user experiences are not. +- Follow the installed-draft, OAuth, reviewer-fixture, and evidence sequence in + `GOOGLE_MARKETPLACE_PLAYBOOK.md` for every candidate. ## Measurement contract @@ -44,6 +46,9 @@ views**, segmented by product. `utm_medium=addon`, and a unique `utm_campaign`. 4. Signup and paid conversion are joined to that campaign in the existing first-party attribution pipeline. +5. Public tutorial links use the YouTube UTM contract in + `YOUTUBE_PROMOTION.md`, allowing YouTube sessions, signups, and first + activations to be compared with Marketplace acquisition. The add-ons do not send spreadsheet contents, formulas, cell values, Google account identifiers, or API keys for analytics. @@ -73,3 +78,7 @@ Run clasp from the selected product directory so its `.claspignore` exposes only The first two have the clearest non-overlapping search intent and quickest time-to-value. Start their test deployments first, measure activation, then promote the strongest funnel before submitting the next Marketplace listing. + +Do not submit all five simultaneously. Complete the original add-on's OAuth +review, then take Crack Spread Lab through the full installed-draft smoke and +evidence packet before cloning the operational sequence for the next product. diff --git a/README.md b/README.md index 932a575..6dc0fcf 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,11 @@ Sheets™. > Google Workspace Marketplace publication is pending. The public listing was > submitted on July 26, 2026, rejected on July 27 pending trademark attribution -> and OAuth verification remediation, and resubmitted on July 29 after the -> listing, homepage, scopes, and Apps Script version were reconciled. Google -> Cloud currently reports that the draft is in review. Do not claim Marketplace -> availability until Google approves and publishes the listing. +> and OAuth verification remediation, and resubmitted on July 29. Google then +> placed the Marketplace review on hold pending OAuth approval. OAuth branding +> was verified and Data Access verification was submitted on July 30. Do not +> resubmit Marketplace or claim availability until Google approves OAuth and +> publishes the listing. Dataset access, history, freshness, and limits depend on the API key, source, and account entitlement. Review the @@ -129,6 +130,11 @@ that version in the Marketplace SDK. Prepared listing copy, scope justifications, required screenshot shots, and generated assets are in [MARKETPLACE_LISTING.md](MARKETPLACE_LISTING.md). +The reusable launch and troubleshooting lessons are in +[GOOGLE_MARKETPLACE_PLAYBOOK.md](GOOGLE_MARKETPLACE_PLAYBOOK.md). The separate +public-video acquisition experiment is in +[YOUTUBE_PROMOTION.md](YOUTUBE_PROMOTION.md). + ## Canonical links - [Product facts](https://api.oilpriceapi.com/product-facts.json) diff --git a/YOUTUBE_PROMOTION.md b/YOUTUBE_PROMOTION.md new file mode 100644 index 0000000..f16eccb --- /dev/null +++ b/YOUTUBE_PROMOTION.md @@ -0,0 +1,205 @@ +# Public YouTube promotion plan + +## Recommendation + +Create a separate public promotional video for OilPriceAPI. + +The business case is YouTube search discovery, branded visibility, qualified +referral traffic, and measurable signup or workbook activation. Do not model +the description URL as a guaranteed high-authority SEO backlink. Treat any +indexing or link-equity benefit as secondary. + +Keep the OAuth reviewer video Unlisted. It contains review-specific screens and +an account identifier and is not the polished public asset. + +YouTube's own guidance says unique, keyword-relevant titles and descriptions +help viewers find videos through search. Long-form descriptions can contain +clickable external links when the channel has advanced features enabled: + +- `https://support.google.com/youtube/answer/12948449?hl=en` +- `https://support.google.com/youtube/answer/13748639?hl=en` + +## Publish timing + +Two useful videos can be produced without making premature Marketplace claims. + +### Video 1: publish before Marketplace approval + +Focus on the underlying workflow and OilPriceAPI: + +> How to Pull WTI and Brent Prices into Google Sheets™ | OilPriceAPI Tutorial + +Show a clean spreadsheet, API setup, live values, units, source timestamps, and +freshness. State accurately that Marketplace publication is pending. Use a +signup or integration-page CTA, not an “Install now” CTA. + +### Video 2: publish after Marketplace approval + +Focus on discovery and installation: + +> OilPriceAPI Google Sheets™ Add-on: Install, Configure, and Build a Live Oil +> Price Sheet + +Show the public Marketplace listing, installation, **Use in this document**, +configuration, formulas, batch fetch, and a finished workbook. + +## Trademark and policy guardrails + +- Use `OilPriceAPI` as the brand and publisher identity. +- Use `Google Sheets™` only to describe compatibility or the demonstrated + workflow. +- Do not use Google branding in a way that implies sponsorship, certification, + endorsement, or ownership. +- Do not place `Google`, `Google Sheets`, or another Google product trademark + in the Marketplace product name for future portfolio products. +- Add this description footer: + +> Google Sheets™ is a trademark of Google LLC. OilPriceAPI is not affiliated +> with or endorsed by Google LLC. + +- Do not claim the add-on is available from Marketplace before Google publishes + the listing. + +## Public recording standard + +Record a new public asset; do not republish the OAuth reviewer video. + +- Use a dedicated demo account and a blank, professionally formatted workbook. +- Show no email address, OAuth warning page, API key, password manager, + notifications, unrelated tabs, or capture controls. +- Use narration or concise on-screen callouts. +- Record at 1080p, 30 fps, and target 4–7 minutes. +- Lead with the finished outcome in the first 10 seconds. +- Demonstrate WTI, Brent, and natural gas, then source timestamp and unit + metadata. +- Include one recovery state, such as a missing key, followed by the successful + next action. +- End with one CTA. + +## Search packaging + +Primary topic: + +`oil price data in Google Sheets` + +Supporting phrases: + +- WTI price Google Sheets; +- Brent crude price spreadsheet; +- oil price API tutorial; +- energy market data spreadsheet; +- commodity price formulas; +- live oil prices in a spreadsheet. + +Use one primary phrase naturally in the title and first two description lines. +Use YouTube Analytics Research to validate the final wording before recording. +Do not stuff tags or repeat exact-match phrases unnaturally. + +Suggested thumbnail: + +- real spreadsheet crop with WTI and Brent values; +- OilPriceAPI droplet mark; +- 3–5 words: `LIVE OIL DATA → SHEETS`; +- no Google logo and no endorsement-style badge. + +Suggested chapters: + +```text +00:00 Live oil-price workbook +00:15 What OilPriceAPI provides +00:40 Configure the spreadsheet +01:30 WTI and Brent formulas +02:30 Units, sources, and timestamps +03:30 Fetch a market-data table +04:30 Common errors and recovery +05:15 Next step +``` + +## Description template + +```text +Pull WTI, Brent, natural-gas, and other energy-market data into Google Sheets™ +with OilPriceAPI. This tutorial shows live price formulas, units, source +timestamps, freshness metadata, and a multi-commodity table. + +Start here: +https://www.oilpriceapi.com/integrations/google-sheets?utm_source=youtube&utm_medium=organic_video&utm_campaign=google_sheets_addon&utm_content=overview_demo + +Create an OilPriceAPI account: +https://www.oilpriceapi.com/auth/signup?utm_source=youtube&utm_medium=organic_video&utm_campaign=google_sheets_addon&utm_content=signup_cta + +Documentation: +https://docs.oilpriceapi.com + +Google Sheets™ is a trademark of Google LLC. OilPriceAPI is not affiliated +with or endorsed by Google LLC. +``` + +Confirm that both destination URLs preserve UTM parameters through redirects +before publishing. + +## Measurement contract + +Google Analytics recommends consistent `utm_source`, `utm_medium`, and +`utm_campaign` values so referral sessions appear in Traffic acquisition: + +`https://support.google.com/analytics/answer/10917952?hl=en` + +Use: + +```text +utm_source=youtube +utm_medium=organic_video +utm_campaign=google_sheets_addon +utm_content= +``` + +Track weekly for 90 days: + +| Funnel stage | Metric | Source | +| --- | --- | --- | +| Discovery | Impressions and YouTube Search traffic | YouTube Analytics | +| Packaging | Impression click-through rate | YouTube Analytics | +| Engagement | Average percentage viewed and 30-second retention | YouTube Analytics | +| Intent | Description-link clicks or GA4 sessions | YouTube/GA4 | +| Acquisition | Signups attributed to the UTM campaign | First-party attribution | +| Activation | First successful add-on/API request with product client header | OilPriceAPI logs | +| Revenue | Paid conversions attributed to the campaign | Billing attribution | + +North-star metrics: + +1. activated OilPriceAPI accounts per 1,000 video views; +2. activated workbooks per 100 YouTube-referred landing-page sessions. + +Use a unique `utm_content` for each CTA and video, such as: + +- `overview_demo_description`; +- `install_tutorial_description`; +- `pinned_comment`; +- `channel_profile`. + +Do not put email addresses, Google account IDs, spreadsheet contents, API keys, +or other user data into analytics events. + +## 30-day launch test + +1. Publish one outcome-led tutorial. +2. Add the tracked integration-page link to the first two description lines. +3. Add a tracked signup link below it. +4. Add the integration-page link to the channel profile. +5. Publish one short excerpt that points to the long-form related video; Shorts + description URLs are not clickable. +6. Review YouTube search terms and audience retention after 7 and 30 days. +7. Compare YouTube-referred signup and activation rates with Marketplace and + organic-search traffic. +8. Produce the next portfolio-product video only if the first video produces + qualified visits or activation signal, not merely views. + +Success threshold for the first experiment: + +- at least 100 qualified landing-page sessions, or +- at least 10 attributed signups, or +- at least 3 first API/add-on activations + +within 90 days. If none occur, revise the topic, thumbnail, CTA, or landing-page +match before scaling the series. diff --git a/test/public-claims.test.js b/test/public-claims.test.js index bd80b40..f87173a 100644 --- a/test/public-claims.test.js +++ b/test/public-claims.test.js @@ -13,6 +13,8 @@ const PUBLIC_FILES = [ "FetchDialog.html", "MARKETPLACE_LISTING.md", "OAUTH_VERIFICATION.md", + "GOOGLE_MARKETPLACE_PLAYBOOK.md", + "YOUTUBE_PROMOTION.md", "docs/index.html", "package.json", "test/README.md", @@ -55,11 +57,21 @@ test("operator records preserve the exact release and Google submission state", ); const records = `${deployment}\n${listing}\n${oauth}`; - assert.match(deployment, /Runtime version: `1\.2\.1`/); - assert.match(deployment, /Current immutable Apps Script version: `10`/); + assert.match(deployment, /Runtime version: `1\.2\.2`/); + assert.match(deployment, /Current immutable Apps Script version: `11`/); assert.match(listing, /Marketplace draft Apps Script version: `9`/); - assert.match(listing, /OAuth submission state: \*\*not submitted\*\*/); - assert.match(oauth, /OAuth verification has \*\*not been submitted\*\*/); + assert.match( + listing, + /OAuth submission state: \*\*submitted July 30, 2026 — under review\*\*/, + ); + assert.match( + oauth, + /OAuth data access was \*\*submitted July 30, 2026 and is under review\*\*/, + ); + assert.match( + records, + /https:\/\/youtu\.be\/FakNSmBddhE/, + ); assert.match( records, /https:\/\/github\.com\/OilpriceAPI\/google-sheets-addin\/issues\/20/, From 7593a28f1e9335b7b3691416d72ba83740bf0f81 Mon Sep 17 00:00:00 2001 From: Karl Waldman Date: Thu, 30 Jul 2026 10:32:43 -0400 Subject: [PATCH 2/2] feat: prepare Sheets add-on portfolio candidates --- PORTFOLIO.md | 20 +- PORTFOLIO_SUBMISSION_READINESS.md | 90 +++++++ README.md | 5 + .../bunker-voyage-planner/app-icon-128.png | Bin 0 -> 2454 bytes .../bunker-voyage-planner/app-icon-32.png | Bin 0 -> 699 bytes .../bunker-voyage-planner/app-icon.svg | 6 + .../card-banner-220x140.png | Bin 0 -> 7869 bytes .../bunker-voyage-planner/card-banner.svg | 10 + .../screenshots/README.md | 3 + .../crack-spread-lab/app-icon-128.png | Bin 0 -> 2537 bytes .../crack-spread-lab/app-icon-32.png | Bin 0 -> 703 bytes .../marketplace/crack-spread-lab/app-icon.svg | 6 + .../crack-spread-lab/card-banner-220x140.png | Bin 0 -> 7454 bytes .../crack-spread-lab/card-banner.svg | 10 + .../crack-spread-lab/screenshots/README.md | 3 + .../energy-curve-builder/app-icon-128.png | Bin 0 -> 2304 bytes .../energy-curve-builder/app-icon-32.png | Bin 0 -> 664 bytes .../energy-curve-builder/app-icon.svg | 6 + .../card-banner-220x140.png | Bin 0 -> 7507 bytes .../energy-curve-builder/card-banner.svg | 10 + .../screenshots/README.md | 3 + .../fuel-surcharge-studio/app-icon-128.png | Bin 0 -> 2375 bytes .../fuel-surcharge-studio/app-icon-32.png | Bin 0 -> 681 bytes .../fuel-surcharge-studio/app-icon.svg | 6 + .../card-banner-220x140.png | Bin 0 -> 7817 bytes .../fuel-surcharge-studio/card-banner.svg | 10 + .../screenshots/README.md | 3 + .../gas-spread-monitor/app-icon-128.png | Bin 0 -> 2546 bytes .../gas-spread-monitor/app-icon-32.png | Bin 0 -> 659 bytes .../gas-spread-monitor/app-icon.svg | 6 + .../card-banner-220x140.png | Bin 0 -> 7723 bytes .../gas-spread-monitor/card-banner.svg | 10 + .../gas-spread-monitor/screenshots/README.md | 3 + package.json | 3 + portfolio/dist/bunker-voyage-planner/Code.gs | 132 +++++++-- .../MARKETPLACE_LISTING.md | 24 +- .../bunker-voyage-planner/REVIEWER_GUIDE.md | 48 ++++ .../SUBMISSION_CHECKLIST.md | 40 +++ .../dist/bunker-voyage-planner/Sidebar.html | 23 +- portfolio/dist/crack-spread-lab/Code.gs | 125 +++++++-- .../crack-spread-lab/MARKETPLACE_LISTING.md | 24 +- .../dist/crack-spread-lab/REVIEWER_GUIDE.md | 48 ++++ .../crack-spread-lab/SUBMISSION_CHECKLIST.md | 40 +++ portfolio/dist/crack-spread-lab/Sidebar.html | 23 +- portfolio/dist/energy-curve-builder/Code.gs | 153 ++++++++--- .../MARKETPLACE_LISTING.md | 24 +- .../energy-curve-builder/REVIEWER_GUIDE.md | 48 ++++ .../SUBMISSION_CHECKLIST.md | 40 +++ .../dist/energy-curve-builder/Sidebar.html | 23 +- portfolio/dist/fuel-surcharge-studio/Code.gs | 140 ++++++++-- .../MARKETPLACE_LISTING.md | 24 +- .../fuel-surcharge-studio/REVIEWER_GUIDE.md | 48 ++++ .../SUBMISSION_CHECKLIST.md | 40 +++ .../dist/fuel-surcharge-studio/Sidebar.html | 23 +- portfolio/dist/gas-spread-monitor/Code.gs | 135 ++++++++-- .../gas-spread-monitor/MARKETPLACE_LISTING.md | 24 +- .../dist/gas-spread-monitor/REVIEWER_GUIDE.md | 48 ++++ .../SUBMISSION_CHECKLIST.md | 40 +++ .../dist/gas-spread-monitor/Sidebar.html | 23 +- portfolio/products.json | 20 ++ .../products/bunker-voyage-planner/Product.gs | 11 + .../products/crack-spread-lab/Product.gs | 4 + .../products/energy-curve-builder/Product.gs | 32 ++- .../products/fuel-surcharge-studio/Product.gs | 19 +- .../products/gas-spread-monitor/Product.gs | 14 +- portfolio/releases.json | 44 +-- portfolio/runtime/Core.gs | 117 ++++++-- portfolio/runtime/Sidebar.html | 23 +- scripts/build-portfolio.js | 134 ++++++++- scripts/generate-marketplace-assets.js | 69 ++++- scripts/verify-apps-script-releases.js | 61 +++++ scripts/verify-marketplace-assets.js | 45 +++- scripts/verify-portfolio-links.js | 46 ++++ scripts/verify-portfolio.js | 34 ++- test/portfolio-live-smoke.js | 170 ++++++++++++ test/portfolio.test.js | 255 +++++++++++++++++- 76 files changed, 2403 insertions(+), 268 deletions(-) create mode 100644 PORTFOLIO_SUBMISSION_READINESS.md create mode 100644 assets/marketplace/bunker-voyage-planner/app-icon-128.png create mode 100644 assets/marketplace/bunker-voyage-planner/app-icon-32.png create mode 100644 assets/marketplace/bunker-voyage-planner/app-icon.svg create mode 100644 assets/marketplace/bunker-voyage-planner/card-banner-220x140.png create mode 100644 assets/marketplace/bunker-voyage-planner/card-banner.svg create mode 100644 assets/marketplace/bunker-voyage-planner/screenshots/README.md create mode 100644 assets/marketplace/crack-spread-lab/app-icon-128.png create mode 100644 assets/marketplace/crack-spread-lab/app-icon-32.png create mode 100644 assets/marketplace/crack-spread-lab/app-icon.svg create mode 100644 assets/marketplace/crack-spread-lab/card-banner-220x140.png create mode 100644 assets/marketplace/crack-spread-lab/card-banner.svg create mode 100644 assets/marketplace/crack-spread-lab/screenshots/README.md create mode 100644 assets/marketplace/energy-curve-builder/app-icon-128.png create mode 100644 assets/marketplace/energy-curve-builder/app-icon-32.png create mode 100644 assets/marketplace/energy-curve-builder/app-icon.svg create mode 100644 assets/marketplace/energy-curve-builder/card-banner-220x140.png create mode 100644 assets/marketplace/energy-curve-builder/card-banner.svg create mode 100644 assets/marketplace/energy-curve-builder/screenshots/README.md create mode 100644 assets/marketplace/fuel-surcharge-studio/app-icon-128.png create mode 100644 assets/marketplace/fuel-surcharge-studio/app-icon-32.png create mode 100644 assets/marketplace/fuel-surcharge-studio/app-icon.svg create mode 100644 assets/marketplace/fuel-surcharge-studio/card-banner-220x140.png create mode 100644 assets/marketplace/fuel-surcharge-studio/card-banner.svg create mode 100644 assets/marketplace/fuel-surcharge-studio/screenshots/README.md create mode 100644 assets/marketplace/gas-spread-monitor/app-icon-128.png create mode 100644 assets/marketplace/gas-spread-monitor/app-icon-32.png create mode 100644 assets/marketplace/gas-spread-monitor/app-icon.svg create mode 100644 assets/marketplace/gas-spread-monitor/card-banner-220x140.png create mode 100644 assets/marketplace/gas-spread-monitor/card-banner.svg create mode 100644 assets/marketplace/gas-spread-monitor/screenshots/README.md create mode 100644 portfolio/dist/bunker-voyage-planner/REVIEWER_GUIDE.md create mode 100644 portfolio/dist/bunker-voyage-planner/SUBMISSION_CHECKLIST.md create mode 100644 portfolio/dist/crack-spread-lab/REVIEWER_GUIDE.md create mode 100644 portfolio/dist/crack-spread-lab/SUBMISSION_CHECKLIST.md create mode 100644 portfolio/dist/energy-curve-builder/REVIEWER_GUIDE.md create mode 100644 portfolio/dist/energy-curve-builder/SUBMISSION_CHECKLIST.md create mode 100644 portfolio/dist/fuel-surcharge-studio/REVIEWER_GUIDE.md create mode 100644 portfolio/dist/fuel-surcharge-studio/SUBMISSION_CHECKLIST.md create mode 100644 portfolio/dist/gas-spread-monitor/REVIEWER_GUIDE.md create mode 100644 portfolio/dist/gas-spread-monitor/SUBMISSION_CHECKLIST.md create mode 100644 scripts/verify-apps-script-releases.js create mode 100644 scripts/verify-portfolio-links.js create mode 100644 test/portfolio-live-smoke.js diff --git a/PORTFOLIO.md b/PORTFOLIO.md index 73e89c5..6ea230e 100644 --- a/PORTFOLIO.md +++ b/PORTFOLIO.md @@ -4,6 +4,10 @@ This repository contains five distinct Google Workspace Marketplace candidates. They share a security and release runtime, but each product creates a different finished workbook for a different buyer and workflow. +All five now have immutable Apps Script 1.0.0 candidates. See +`PORTFOLIO_SUBMISSION_READINESS.md` for the exact Script IDs, versions, +remaining Google Console evidence, and hold point. + | Product | Primary buyer | Activated outcome | Acquisition keyword wedge | | --- | --- | --- | --- | | Crack Spread Lab by OilPriceAPI | Refinery analyst or energy trader | Live refinery-margin workbook with history and sensitivity | crack spread spreadsheet | @@ -63,6 +67,17 @@ npm run portfolio:verify npm run validate ``` +Account-independent release verification: + +```bash +npm run test:portfolio:live +npm run portfolio:verify:remote +npm run portfolio:verify:links +``` + +The live smoke requires `OILPRICEAPI_KEY` to be set to a synthetic non-customer +test credential. It never prints the credential. + Deployable Apps Script roots are written to `portfolio/dist//`. Run clasp from the selected product directory so its `.claspignore` exposes only `Code.gs`, `Sidebar.html`, and `appsscript.json`. @@ -76,8 +91,9 @@ Run clasp from the selected product directory so its `.claspignore` exposes only 5. Gas Spread Monitor The first two have the clearest non-overlapping search intent and quickest -time-to-value. Start their test deployments first, measure activation, then -promote the strongest funnel before submitting the next Marketplace listing. +time-to-value. Their Apps Script candidates are already versioned. Prepare the +Cloud/OAuth/Marketplace draft for Crack Spread Lab first, measure activation, +then promote the strongest funnel before submitting the next listing. Do not submit all five simultaneously. Complete the original add-on's OAuth review, then take Crack Spread Lab through the full installed-draft smoke and diff --git a/PORTFOLIO_SUBMISSION_READINESS.md b/PORTFOLIO_SUBMISSION_READINESS.md new file mode 100644 index 0000000..9b21aad --- /dev/null +++ b/PORTFOLIO_SUBMISSION_READINESS.md @@ -0,0 +1,90 @@ +# Google Workspace portfolio submission readiness + +Reviewed: July 30, 2026 + +## Release candidates + +All five products have distinct code, titles, workflows, Apps Script projects, +immutable versions, listing copy, reviewer guides, least-privilege manifests, +activation headers, UTM campaigns, and graphic asset sets. None has been +submitted to Google Workspace Marketplace. + +| Rollout | Product | Apps Script candidate | Planned Cloud project | Remaining account-bound evidence | +| --- | --- | --- | --- | --- | +| 1 | Crack Spread Lab by OilPriceAPI | [Script](https://script.google.com/d/1c2O84bJoprkUtyo8eHb-yYmz1Mtttpo-8miUrKf3o3rzrVXUBfFU8M5C/edit), version 3 | `oilpriceapi-crack-spread` | Cloud/OAuth/Marketplace draft, installed-draft smoke, screenshot, video | +| 2 | Fuel Surcharge Studio by OilPriceAPI | [Script](https://script.google.com/d/1Mii2a-nGgRmrnsV1rl_9wElmZfBrmhJgufYPqvDEjvN_s9xTQ8WHRtxN/edit), version 4 | `oilpriceapi-fuel-surcharge` | Cloud/OAuth/Marketplace draft, installed-draft smoke, screenshot, video | +| 3 | Energy Curve Builder by OilPriceAPI | [Script](https://script.google.com/d/1q3YQIyE17nv4uNLQV7Pdw3DwKFu8Yrp9Uwylxw6WPmWhwyKzv4xZZDLZ/edit), version 1 | `oilpriceapi-energy-curve` | Cloud/OAuth/Marketplace draft, installed-draft smoke, screenshot, video | +| 4 | Bunker Voyage Planner by OilPriceAPI | [Script](https://script.google.com/d/1aQLBBWhyd_ffw1h9gmVFromI-5MwQsGd2v9REoUhXOHziDgbTA2eqY-u/edit), version 2 | `oilpriceapi-bunker-voyage` | Cloud/OAuth/Marketplace draft, installed-draft smoke, screenshot, video | +| 5 | Gas Spread Monitor by OilPriceAPI | [Script](https://script.google.com/d/1Od5dLY-A8l-sULQidIuso3rJRjJvWIaI54JZpPQlCVyAk32phv2D1YSU/edit), version 2 | `oilpriceapi-gas-spread` | Cloud/OAuth/Marketplace draft, installed-draft smoke, screenshot, video | + +## Verified now + +- The complete shared runtime and every product-specific builder compile. +- Every customer-critical build produces the documented workbook tabs and + editable Google Sheets formulas. +- Missing, revoked, unentitled, rate-limited, timed-out, malformed, and + incomplete API responses produce recovery messages or fail closed. +- The Energy Curve Builder uses the production + `/v1/futures/ice-wti/curve` and `/v1/futures/ice-brent/curve` contracts. +- A production API smoke built all five workbook models with current, + source-timestamped non-customer test data. +- Remote verification re-cloned every recorded immutable Apps Script version + and matched its code, sidebar, and manifest to the local release package. +- All product landing, signup, pricing, privacy, terms, and support links return + successful first-party responses. +- Each application name is within the 50-character Marketplace limit; every + short description is within the 200-character limit. +- Each manifest requests only: + `spreadsheets.currentonly`, `script.external_request`, and + `script.container.ui`. +- Each product has unique 32px and 128px icons and a unique 220×140 card + banner. Screenshots are intentionally not simulated or reused. + +## Remaining console pass + +Repeat this sequence for one product at a time: + +1. Create the separate standard Google Cloud project shown in the table. +2. Enable the Apps Script API and Google Workspace Marketplace SDK. +3. Link the standalone Apps Script project to the standard Cloud project's + numeric project number. +4. Configure External OAuth branding using the exact Marketplace application + name, an organization-controlled support contact, the product guide, and the + shared Workspace privacy and terms pages. +5. Add only the three manifest scopes. Keep the project in Testing while the + draft and reviewer fixture are prepared. +6. Configure a Public Editor add-on Marketplace draft with the recorded Script + ID and immutable version. Public visibility is permanent after it is saved. +7. Install the Marketplace draft—not only the Apps Script editor test + deployment—select **Use in this document**, and refresh the blank test + spreadsheet. +8. Run the product's `REVIEWER_GUIDE.md` with a synthetic non-customer API key, + inspect Apps Script executions, and confirm all documented recovery states. +9. Capture a unique, full-bleed 1280×800 PNG from the exact installed build and + record a scope-complete OAuth demonstration video. +10. Stop before OAuth or Marketplace submission until the preceding product's + Google review feedback has been applied across the shared runtime. + +## Hold and rollout decision + +The original OilPriceAPI listing remains the policy canary. Do not submit these +five listings in parallel. Once the original clears OAuth and Marketplace +review: + +1. Convert any reviewer feedback into shared tests and rebuild all pending + packages. +2. Take Crack Spread Lab through OAuth verification and Marketplace review. +3. Measure listing views, installs, attributed signups, and first workbook + activations. +4. Continue in rollout order only if the prior product is policy-clean and the + acquisition funnel is producing qualified activity. + +This sequencing preserves the search-intent experiment without multiplying an +unknown policy or runtime defect across five live listings. + +## Current Google references + +- [Publish an add-on](https://developers.google.com/workspace/add-ons/how-tos/publish-add-on-overview) +- [Create a Marketplace store listing](https://developers.google.com/workspace/marketplace/create-listing) +- [Marketplace app review requirements](https://developers.google.com/workspace/marketplace/about-app-review) +- [OAuth verification requirements](https://support.google.com/cloud/answer/13464321) diff --git a/README.md b/README.md index 6dc0fcf..7db7a0d 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,11 @@ The reusable launch and troubleshooting lessons are in public-video acquisition experiment is in [YOUTUBE_PROMOTION.md](YOUTUBE_PROMOTION.md). +The five follow-on products and rollout order are in +[PORTFOLIO.md](PORTFOLIO.md). Their immutable Apps Script candidates and +remaining Google Console gates are tracked in +[PORTFOLIO_SUBMISSION_READINESS.md](PORTFOLIO_SUBMISSION_READINESS.md). + ## Canonical links - [Product facts](https://api.oilpriceapi.com/product-facts.json) diff --git a/assets/marketplace/bunker-voyage-planner/app-icon-128.png b/assets/marketplace/bunker-voyage-planner/app-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..990774380f795c363bc375770d4dc6ab724484f6 GIT binary patch literal 2454 zcmV;H32F9;P)Bm zWp{=BV6HiDna2#X^LA$U&D=Ndos;~M&3pS^?mNHVoO{nXH}6@N7Z$CX%mm!SCDbwj zTe(o}T&lxdLO++F2{0BP^xM7^s@?W2`Z%iYMxR?oEd~>rsGGy2dXESOBO+%4TA73@ zySi2PWtoKENO4Vymk5 z3~$L7_0ySDd&%)o7UE+9_E<5v+!OA(sQVrL>Y*HQltvQF;gAp&Ihe842kv~Y`&>46ZykPlRib#k8>ks!n8116ybBftUE z10jGJ9|+Zs0_Y?HAfzHx9Tu0W&-_3L+(-m~;}`*m02y8%kN~845=a0(a0Cx2C;~|H zB#;1n;0PX4Py~?XNgx6Ez!5y8pa>w%lRyISfg^ZGK@mWjCxHY|Civ0(HTtrL*6TB3 z3FBj-2vA~2on5s`U%PCRu8245s~_2@&;Rpk4Puo z@zL^*1|$GEvXd@exGszQ6n?&RgFbI=gK;8B07Y`pd2<`|b&n2({#?NGm;7a&9Vtu` zN)f|eMW`RTZF$;fH~A_2;?Yfd>VhWY#F79! zNck!XBtIQ97QLj;s;W0mFhzhdrhM1`y+t>)9@IyV_1nO|@mM}0zK2~!i3U$?|0s(zJXUCz zFPD)3ibVbo?{BozqI?|e77aRkj%SgEk-#nGt0(nOr_ioqs04u3IBb`WbzV)623`?o>OP?n0RCzV z*?5_6G%!V61qmR3@^P2U&mMl+Q}Rs{S3v^EzZip^uwsx;0swzc0k|XPr;FU1FeCs7 z<{AJya5zU7lYEK*;9miZRE8bPHf$0A_&WmpEDa2n%r)9^I!@ypRCmBmjy4af$#m1;l9zpfy08)&R5!jME~JvVb^c0h9*DDGj7L zK%D9TR0zspR0yIuf~gmvsTV-aAWh96kGg?gwS&w3K0K`5K(980l>2>n zSOwtKmauZa4-cyV2+bq_5UzV!hKK$<6k1mFWl@Q{KcfHY46 z3BW@qJhWlAe)g@8v#?^@hkEg*J$mMIJ2GA;cf;c?&i~w7_g2pPE@|HD{GVSw_d*e1 z7$@xhZ|4Z>Y_0cwaoBk!`$Y?z^x%o4ef;1z-NSyu1z#(FB02Owt)F%nCyfM@0M|vF*JpcA5&NbUU`k!)9z zhJ6q5p{`^0`Oi9goa29hw;h5p2S5F~H`@={=LSwB^^X^13XO_ zy?q1r@h`sZ&G@b#P7$f_k5R-lVI+V&1<-wbAY)&_O;2pm%eU@#e(AFDos8?&zW=Fn z?&cL+oa0q%-gS=u-er+kt_bBKaOviKS=Zh6^mga`${lUa@f{z3?Fj1J>V_Z+K=p-9 z0J!O5cH&=KKI>kHlrO|NZq5`#jF;*DTwZaqZJ@v^gUAb?qxUw)#}~zvUmV8hZPS(!|$KhJTC*e*OIWm>^WU#RO~(lK}phmhvs%xuO4FM!HJF4Ji~zMk6hKekfE~sWx2}BEE&5Ct%jwe0_L*s?Fa!am*F4s{uSrec9b)LoV}yKxeiDj9SEq`n{c z!B^kfbHCdcyLWy5jdR_iO?!+#Q*oe@$i0Qe1hkqT5P_G_2TZ~{Hu<&yBvcUr5GoNe z2~kG?RtzR`p?XXYiU6hYflxij`6&Td7FGmnhyVzQ2-Oo-mPOQ5iUJS@9JQi#ld}b2 zuLaZs9}JKni{}F-V4juJDxVq8q}oj+Kp;iPgxZB1Tdo#OOyfdznI0?wy!C-l-Il1I z?oNJcK^ReYaH$4~1PF)_)gTw@w|SADwjQW7KX3vcJNZc_)Sn6>-w{DHF^dZ|Ktw>9 zict-+h>BQaF)kCgW9nf3VFUjUQ11q5KLIZrs3f*@ znMlN}+0UJC88wl(kV%+{4J9UE3zw>GXt!~&wwxOx8hwbqY&+@O8uT^#+zaZy2ic#v Ubuy&Qr2qf`07*qoM6N<$g7Ob+Qvd(} literal 0 HcmV?d00001 diff --git a/assets/marketplace/bunker-voyage-planner/app-icon-32.png b/assets/marketplace/bunker-voyage-planner/app-icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..32e0b340848fcdef60d506a635f6c58a3a3e19cc GIT binary patch literal 699 zcmV;s0!00ZP)#2FMdLse`!0o0BP!MaxZDa3dL#I@ zx{B+GIR-$z!4tuj&byTbsA`e1|8j^4QoE6SqXFc7@5OuUev}2U`E-D_bz}Poi+7(N z$4P$@JI{`3LhAugr}x4zz^3*Q)=rv;Ub5{yD*B>n*7CI?zXJ3{Gg$oasXPFeTfqTO z__vCN<2fc+3V^Qjdkg{8hzjcae~5=N3pk~u{+PUC0CnB!UnTz!&}hXGeE#wkgU?=T zFaT?L14OcmFw1Bz-2k;+ zK^*ZtF53@ZeT1!@qvrk#{Hb|dOU&Wu&=Xv^KLcm$Eu8QtvFqHJ?p(wOKw^Fg$8W|l z7@I}c#7kCb_|<~0cR&Gv*Z(M`0JQq?nRnRHqav1jkAa6--Qo^j`e^7`9+-X$Uu+f~ z>NJao;yL!WH1sUz1X(n445kexgbk%~+LqC*Dd;Q=rxrpK52tx+3OZ>^=($v6TH>W; hPLNWp?ONr8d + + + + BV + diff --git a/assets/marketplace/bunker-voyage-planner/card-banner-220x140.png b/assets/marketplace/bunker-voyage-planner/card-banner-220x140.png new file mode 100644 index 0000000000000000000000000000000000000000..02114fa5886d5c405f8fec353a3ee47f90dc0a94 GIT binary patch literal 7869 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 + + + + + Bunker Voyage + Planner + Purpose-built workflow + by OilPriceAPI + diff --git a/assets/marketplace/bunker-voyage-planner/screenshots/README.md b/assets/marketplace/bunker-voyage-planner/screenshots/README.md new file mode 100644 index 0000000..f36448f --- /dev/null +++ b/assets/marketplace/bunker-voyage-planner/screenshots/README.md @@ -0,0 +1,3 @@ +# Bunker Voyage Planner by OilPriceAPI screenshot evidence + +Capture at least one full-bleed 1280×800 PNG from the exact immutable installed Marketplace draft after completing the real-account smoke in REVIEWER_GUIDE.md. Do not reuse or simulate another product's screenshot. diff --git a/assets/marketplace/crack-spread-lab/app-icon-128.png b/assets/marketplace/crack-spread-lab/app-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..dde8fd2b9295cbe947830eaa335d351fe28e9921 GIT binary patch literal 2537 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 literal 0 HcmV?d00001 diff --git a/assets/marketplace/crack-spread-lab/app-icon.svg b/assets/marketplace/crack-spread-lab/app-icon.svg new file mode 100644 index 0000000..6d6d0cb --- /dev/null +++ b/assets/marketplace/crack-spread-lab/app-icon.svg @@ -0,0 +1,6 @@ + + + + + 321 + diff --git a/assets/marketplace/crack-spread-lab/card-banner-220x140.png b/assets/marketplace/crack-spread-lab/card-banner-220x140.png new file mode 100644 index 0000000000000000000000000000000000000000..e3f05ec826cbce8a70cf42696e61d1398f5d8b5d GIT binary patch literal 7454 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 i{(mmh{V!O*Qq0<%TK + + + + + Crack Spread + Lab + Purpose-built workflow + by OilPriceAPI + diff --git a/assets/marketplace/crack-spread-lab/screenshots/README.md b/assets/marketplace/crack-spread-lab/screenshots/README.md new file mode 100644 index 0000000..53e01e0 --- /dev/null +++ b/assets/marketplace/crack-spread-lab/screenshots/README.md @@ -0,0 +1,3 @@ +# Crack Spread Lab by OilPriceAPI screenshot evidence + +Capture at least one full-bleed 1280×800 PNG from the exact immutable installed Marketplace draft after completing the real-account smoke in REVIEWER_GUIDE.md. Do not reuse or simulate another product's screenshot. diff --git a/assets/marketplace/energy-curve-builder/app-icon-128.png b/assets/marketplace/energy-curve-builder/app-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..fe262b386f18f4bc26d55321391c615058f28dce GIT binary patch literal 2304 zcmZuzdpHw(8~*JO8ykt3#z@(VTA`vjwK+syhdFg(ws_NN)tVgM$mS4VNz}@r$RQFk zg@t&vQI})nisfu#PQ^&QCe+6t-+$j9_jBLZecjLV-*Y|BO^TZ%T49?4004c&$=-9F zIU7L8t=C4IQ*G;nWICM)20&4D0}${q69WLE{D?i-J3MnX>l_2?pw_emyD!!4Ix5+b zCOdiK8XhUx0^RBSr%LYDm~G@lg90bDyFsFZgfqF@4J$`C=RF%t<0xy9bxDN01O+nU zKL)zqSN1PhHQgC|TVpj| zv_{PIX4Y)tKTDtQN|X>|>>i0z@S{R(UlRFEOD^AVXW9c-SyEfiG@k3bDfa%qiuhw@ zn%fDO{g-P_W{noh@X1u&^so5>yAVOsxN=g6^<=GHlxo_AX=&j6|R! z^f->JF!uFMPj1_Dc{%%zU7E0rq599=0fDPKl{aO}@0E+Oi#?@1_Dw)P2&*6shbe4rXTzsu&OrE1=Ue1 z2%rpaKt1!46aRG31y-98`*PegIbSrQ!8}Q~onvtt@Mv(T02XZwo4F6!Xy6zvIF{Wh zNySF=*!}p~WUHCe5PjYsd4A*~vIpl@{7)6Wd&jYN{KU!lk7|NRCC_|y?}7Kk1;y)2 z=zokn3u*SyoOW_Qzc?&jruyqE0m)yyHS=C>eReR1;%B*_xYqI|qSPhVmjV^M6I*%9 zi?sbwU@AhS@4Z;i^8jrCW3GrRpYz?AF+R%#KA z-U<{4zge@-c!q?cid@(t)+&~^%LqG|N=c@U%^F5O1#@+hm4(Ia2ZrYPp5@VLiH>j( zHvDardT7HagqNu&BQ`P>70Wzy-BBw&P`#Mic@g|imT7_TqHue_oFveQka^n$gL$xD zgRy)S8 z_(xjlFScI`d-sXpX9=O?%R=ZO4XxX4AYk}0bl%>@*KYakA3#tG=<-UFQ>@IP(LZ;T$`R`MoPgRXG**ukyZ-pu$-(zoI zyW%BruDB0T$HcMOSf6*jyOw^qE)SLr{OqQ;My0W2|Bz=7Nj;U82;fPh^V)YwO6gcI z^8G@mSlh#P}9FZvyyK6XN$=vb`_}#OO_YkNaFrp?ON{p@Lall{x`?s`$W#QHR1gq-d;nW zoPZic@PTx6cBJ3D$&pL2l)Mst>tR=T&LL;>v z%4u4&IH0Hj)X%f5g67m-@R=Rs37&H_5ZBNdoXaw+ijfg9tdtxz!=q4|B|WVQoA zD-?z6wjr^6g20^lSH>7CFQ>Oil0B%}wqgZo4e{%Ar! z3I|p9P4bnW)v)6^-y~nsJ{Hhlb%XBE`FEuy=X}ik!95dbbHZ!StRT~x2j!6PWuCjD z-21M?ZoMadBJYt3Z{zTX#qnb8CL1L#;nmi97RF3fkN*uP6u-#sP)+w4GfgB=g#nMA z_cS|h)-J!0vjfK`(cyRLJFfZ} R%&xyQ;E02pJ z-Gp`(j7>zXwiZORQZM{KY()x+f=X$tMWP6PTnXL@JG-0Rr1`?WawggBA|{Z{W&`4Z z1GCF;-e=w!m^qBqL+MA)h@_uXCBBl7cs?O>gqjl1Q*Bk&71Cn<7}=_CRweyI=%Ni_ z^ekz5E00v%2O*pw+anjRXELUrU%2xxYI2F(q>&-eLdMf12DRcLyH^T z1Gsd+!u4Z0j1QPd#v%qt?K1J=#u{da9RV?M(85@6L;zacz|>I->+8U~$0Y$kIW=VA z{0^hD0Ld7~)k8JwDOTq;0NlJhXo>UD7QngfhOkxZtF7qbeiIKh2bsO5C)5bQJr{ui z&h{C?PPbTkkBOHz*M#MMkv{=wPVVQg_5ifFfypBq;%=@yDha`605o>z1OgcA<(~NG z*A>jA@_#MfF@W)b#{2spU^VJ3sFs1{2Sx9j#`Mn*&}>d1fXSg8o@5FlU(FP;Fi{&* z(wP3a-_7RCrwh1t*lHc%>tY#U1=VU9;KFVbw@=w^=PkuXYNrwIUGV;CNgT5KNe9!% zYX@#Voo~5LV1OT=s<@YMaAnZKm&J;>Z0?+~g8`@k9;M&2Rbg*tpNVfv6>%%x9kyF~ zh)}9B&josxe|%QPo7tjpVdk`r>_Q38rwaj}<+82>u`aa6MCnHlwMmf!NP2d33pRPp yX`~ST=F|W9&EZ`04Q2M-O5E0000 + + + + EC + diff --git a/assets/marketplace/energy-curve-builder/card-banner-220x140.png b/assets/marketplace/energy-curve-builder/card-banner-220x140.png new file mode 100644 index 0000000000000000000000000000000000000000..c880a7b4cff974ffcfc0494473b8d7122a4c3068 GIT binary patch literal 7507 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 literal 0 HcmV?d00001 diff --git a/assets/marketplace/energy-curve-builder/card-banner.svg b/assets/marketplace/energy-curve-builder/card-banner.svg new file mode 100644 index 0000000..61bd121 --- /dev/null +++ b/assets/marketplace/energy-curve-builder/card-banner.svg @@ -0,0 +1,10 @@ + + + + + + Energy Curve + Builder + Purpose-built workflow + by OilPriceAPI + diff --git a/assets/marketplace/energy-curve-builder/screenshots/README.md b/assets/marketplace/energy-curve-builder/screenshots/README.md new file mode 100644 index 0000000..0bb352a --- /dev/null +++ b/assets/marketplace/energy-curve-builder/screenshots/README.md @@ -0,0 +1,3 @@ +# Energy Curve Builder by OilPriceAPI screenshot evidence + +Capture at least one full-bleed 1280×800 PNG from the exact immutable installed Marketplace draft after completing the real-account smoke in REVIEWER_GUIDE.md. Do not reuse or simulate another product's screenshot. diff --git a/assets/marketplace/fuel-surcharge-studio/app-icon-128.png b/assets/marketplace/fuel-surcharge-studio/app-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..7272012aed8600581f0be312a992eaafc3fa298a GIT binary patch literal 2375 zcmV-N3Apx&P)KkGODRaeQb1uPQ4~=mh~fi8j7ChXpcO(O7*T)tLW6)U6pP?qbB68G zcDvJ^c4qJF%sI)IG`q8tJLmi6+CV93noD8Lcp6+fb3aSe3q-+N6Py5 zG6n1;$3sDg&y=vk6#Nob$Y-T6n(lfiJP4?ZmJS%%G4cgWWRjoIelY~qJ`wPfQ2|ri zMSeuW6mmQSM0|X@vpc^OMx%Q451hbUiv)Z`j$uFElS!y0=R=?bi0inBi1)Tvam^EP)sJgI$<1m&#=U{D$Ff?c^Z>WcYkwN+`kbU!{8>1kmIIUTLKEnGp#xggy|k(J&?asC^&=rNalf(G;hE zgG2y?QiP`D2T%aVbq|C9dITVN0}6l$km2=$JU2j7fNa}IQ5jR$Etz1E-xOC>-ln>g#0TyLg4fPiAZ~QD55#TvcNV-WT0dyecdwbUw)za`YC(*#q z#UqLU(mv&@YdD%A0Q}79l;GFm5eY!rr+nG0y=w!%6^}>&(mv<L6A59+3c~eah#B z0Q^)uA^}MIl+P;x_@#IhdI9uklJe;gfbQZ^s09$s(&fvy0CX0QLM?#HZMu9q1fZjM zoou|!H5%w5u80JXJNdXv zhQDx!tK{n@u80JXdocz(VP23=0swzc0k|Wk-;(j3kxv2u{|W#*a5zVMNj?bx{40RA z%CH^ThD`zhe+%HM%3Iqc&W&x@6am100>J%mxe({pHf+iQz+VCg3vop*ohM(skN^@S z0Ez$!iU2ePBxnktH9&&a0JI29&?1nsfCOa$lm;d!4Wv3ig6aTN2+Cqq2%<`$qDml@ zf)$m5sTZKA7eLJ*Ma>|Wx`A%BgA4sUEUeu?w>E?n`gvGb1>n|}utGl%3#$MKlSu$1 zg2};(0QkRujp2{X-WsQed_62GF9K-oVGU&VhCU0709t!k1DU;{&qx3vwnIS@08IfQ zp8}dbv5U;!T0YS%5CR~8Z+$=lkorj=0rwFNumhg&#ElZECYd{lEXHRWulBJjh#pEo=H_BWoih;Pj>8@O%NBif1bG6EdeJqXn6?rU=u0yg3lP!(nY+_~nd^s)Qv4yY;X zO08GC<74a>e7@3}|IvOvyx)oly06V$@JfsTCR7 zGs|KZQ+5CaBtl+;i+RIU6Y(=l8GDGL0r(cCgnA}G8sgGUhF4~mLpLVid8!(Me~3TT z$61)Q!smryGl2*8ue4LG-ER+fzO+L7%(9-TwiFx$+B_Tx$RT002ovPDHLkV1iY`SmFQx literal 0 HcmV?d00001 diff --git a/assets/marketplace/fuel-surcharge-studio/app-icon-32.png b/assets/marketplace/fuel-surcharge-studio/app-icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..6ca45c322caaa7b6653cb38e78e0ac67a9158c17 GIT binary patch literal 681 zcmV;a0#^NrP)TSM=HrW2BCI*=aGF!C4 zCKqHE12Pb5v5*$2x4l4ZOL01;!S+1JP9&ex&vIl(fI|Wl?DV!Aj_Yl67(^RvP7ag< zL>g?5Gl(?Ud>%Ll*nDOfu;7PchGF9XsV2w&vMnwH=K!-w5&xZMCjZy%4g4?GU`JmE zXm~vpwz&LvnvwMX-@kwV7o6S*b^r)F&Pe*N z-sMAE2Z%M;{kNGK3$_ph7MN=h&HoMh zg5eh7asUW}(goQLkZ*Mb2PeoNP$JXo4f@}?fA0Uk|Nfy^g6@E>{qz1m{rHM92gtU# z{CAk1h~cBTr`CZj#ODAI8>WUV2S_(N{kNZ<05)LT^+W#)H+5k(oMsNt?F}T#@KFc2 z&?5?d{QmWS_K7ucK8OtxLyiJ`YRGbc10c z&wsmV@uWLo$IT=EsRcl0k?a8N9{>NriwY^mK@0Q$YjpWiw=SsPHTD0(GaLUGZ0z`- zzNQh(23bUoSq$W8+IsyU*mt11VDq&D|I4@b{SRMKLY@Oe$Y_*;9FV-K4%`TdTT%5t zX;tn2O;`7mP*yNHEbX4n@sDQd=4Yud$ zZ8^wJhI%bFMCX)_jzH<0!a= + + + + FS + diff --git a/assets/marketplace/fuel-surcharge-studio/card-banner-220x140.png b/assets/marketplace/fuel-surcharge-studio/card-banner-220x140.png new file mode 100644 index 0000000000000000000000000000000000000000..8e057c73e941cc8af951acf857b10dab96687fc5 GIT binary patch literal 7817 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 literal 0 HcmV?d00001 diff --git a/assets/marketplace/fuel-surcharge-studio/card-banner.svg b/assets/marketplace/fuel-surcharge-studio/card-banner.svg new file mode 100644 index 0000000..73ffb48 --- /dev/null +++ b/assets/marketplace/fuel-surcharge-studio/card-banner.svg @@ -0,0 +1,10 @@ + + + + + + Fuel Surcharge + Studio + Purpose-built workflow + by OilPriceAPI + diff --git a/assets/marketplace/fuel-surcharge-studio/screenshots/README.md b/assets/marketplace/fuel-surcharge-studio/screenshots/README.md new file mode 100644 index 0000000..f567f7a --- /dev/null +++ b/assets/marketplace/fuel-surcharge-studio/screenshots/README.md @@ -0,0 +1,3 @@ +# Fuel Surcharge Studio by OilPriceAPI screenshot evidence + +Capture at least one full-bleed 1280×800 PNG from the exact immutable installed Marketplace draft after completing the real-account smoke in REVIEWER_GUIDE.md. Do not reuse or simulate another product's screenshot. diff --git a/assets/marketplace/gas-spread-monitor/app-icon-128.png b/assets/marketplace/gas-spread-monitor/app-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..36c8073c47d207bf2d887e4c95cb1ac5ce99c012 GIT binary patch literal 2546 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 IM6N<$f|7c^{r~^~ literal 0 HcmV?d00001 diff --git a/assets/marketplace/gas-spread-monitor/app-icon-32.png b/assets/marketplace/gas-spread-monitor/app-icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..6da8d540561585da217239662cc89aa80d9a8e46 GIT binary patch literal 659 zcmV;E0&M+>P)fO%ICc>r&J@m{T?BY$Zi(y8{uzL+iVCQ*>>j{=TN~niec1E+5%zeT z1=y{x$HnwCJ`WBGh=Yy}s9vu#0IDoQ>+J=yS)`Vi1pv9$(}NwKufzZz4-Pv!g&;Nlf`tHRtgo>IpnAQ~y1PYydqqKFZ0wK0O9rs# z_rELne}K3XCCI$I;Cf-fnA4cP<(Ht)8cP6LAb_uvlfsj8b2y)xGUha@tdFz61>o_%tw3w14dH$8KGnz5jx<;bEj!S8+Bnf@n*NIG8SHXHETv zEd%WMe7IX*7pEivoDL1)E)+syd>m?JrFj6w?dfOcx&~MOV|Ja002ovPDHLkV1kRGD=q*4 literal 0 HcmV?d00001 diff --git a/assets/marketplace/gas-spread-monitor/app-icon.svg b/assets/marketplace/gas-spread-monitor/app-icon.svg new file mode 100644 index 0000000..832c679 --- /dev/null +++ b/assets/marketplace/gas-spread-monitor/app-icon.svg @@ -0,0 +1,6 @@ + + + + + GS + diff --git a/assets/marketplace/gas-spread-monitor/card-banner-220x140.png b/assets/marketplace/gas-spread-monitor/card-banner-220x140.png new file mode 100644 index 0000000000000000000000000000000000000000..783d07fce34b66d06b250904560d469548131ffa GIT binary patch literal 7723 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 + + + + + Gas Spread + Monitor + Purpose-built workflow + by OilPriceAPI + diff --git a/assets/marketplace/gas-spread-monitor/screenshots/README.md b/assets/marketplace/gas-spread-monitor/screenshots/README.md new file mode 100644 index 0000000..a0e5da5 --- /dev/null +++ b/assets/marketplace/gas-spread-monitor/screenshots/README.md @@ -0,0 +1,3 @@ +# Gas Spread Monitor by OilPriceAPI screenshot evidence + +Capture at least one full-bleed 1280×800 PNG from the exact immutable installed Marketplace draft after completing the real-account smoke in REVIEWER_GUIDE.md. Do not reuse or simulate another product's screenshot. diff --git a/package.json b/package.json index 5c143e1..2d4988f 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,12 @@ "scripts": { "test": "node --test test/runtime.test.js test/public-claims.test.js", "test:portfolio": "node --test test/portfolio.test.js", + "test:portfolio:live": "node test/portfolio-live-smoke.js", "test:live": "node test/live-smoke.js", "portfolio:build": "node scripts/build-portfolio.js", "portfolio:verify": "node scripts/verify-portfolio.js", + "portfolio:verify:remote": "node scripts/verify-apps-script-releases.js", + "portfolio:verify:links": "node scripts/verify-portfolio-links.js", "assets": "node scripts/generate-marketplace-assets.js", "verify:assets": "node scripts/verify-marketplace-assets.js", "verify:deploy": "node scripts/verify-deploy-package.js", diff --git a/portfolio/dist/bunker-voyage-planner/Code.gs b/portfolio/dist/bunker-voyage-planner/Code.gs index e42c34e..2b70338 100644 --- a/portfolio/dist/bunker-voyage-planner/Code.gs +++ b/portfolio/dist/bunker-voyage-planner/Code.gs @@ -2,6 +2,10 @@ const OPA_PRODUCT = Object.freeze({ "id": "bunker-voyage-planner", "name": "Bunker Voyage Planner by OilPriceAPI", "menu": "Bunker Voyage Planner", + "version": "1.0.0", + "cloudProjectId": "oilpriceapi-bunker-voyage", + "iconMark": "BV", + "brandColor": "#0369A1", "builder": "buildBunkerVoyageWorkbook", "tagline": "Compare port fuel choices and calculate voyage bunker cost.", "landingPath": "/integrations/bunker-voyage-planner", @@ -32,7 +36,7 @@ const OPA_PRODUCT = Object.freeze({ const OPA_API_BASE_URL = 'https://api.oilpriceapi.com/v1'; const OPA_KEY_PROPERTY = 'OILPRICEAPI_KEY'; const OPA_ACTIVATED_PROPERTY = 'OILPRICEAPI_ACTIVATED_AT'; -const OPA_VERSION = '0.1.0'; +const OPA_VERSION = OPA_PRODUCT.version; const OPA_SIGNUP_URL = 'https://www.oilpriceapi.com/auth/signup'; const OPA_ALLOWED_SCOPES = [ 'https://www.googleapis.com/auth/spreadsheets.currentonly', @@ -62,6 +66,7 @@ function showSidebar() { html.productName = OPA_PRODUCT.name; html.tagline = OPA_PRODUCT.tagline; html.builder = OPA_PRODUCT.builder; + html.version = OPA_VERSION; html.signupUrl = signupUrl_(); html.landingUrl = landingUrl_(); SpreadsheetApp.getUi().showSidebar( @@ -89,9 +94,43 @@ function documentProperties_() { } } +function activeSpreadsheetId_() { + try { + const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); + return spreadsheet && typeof spreadsheet.getId === 'function' + ? spreadsheet.getId() + : null; + } catch (error) { + return null; + } +} + +function spreadsheetKeyProperty_() { + const spreadsheetId = activeSpreadsheetId_(); + return spreadsheetId ? `${OPA_KEY_PROPERTY}:${spreadsheetId}` : null; +} + function getApiKey_() { - const properties = documentProperties_(); - return properties ? properties.getProperty(OPA_KEY_PROPERTY) : null; + const documentProperties = documentProperties_(); + const documentKey = documentProperties + ? documentProperties.getProperty(OPA_KEY_PROPERTY) + : null; + if (documentKey) return documentKey; + + // Installed add-on custom functions can run in a separate Apps Script + // authorization context where document properties are unavailable. Keep a + // compatibility copy in owner user properties, namespaced by spreadsheet ID, + // so the key cannot cross into another workbook. + const userProperties = PropertiesService.getUserProperties(); + const spreadsheetKeyProperty = spreadsheetKeyProperty_(); + const spreadsheetKey = spreadsheetKeyProperty + ? userProperties.getProperty(spreadsheetKeyProperty) + : null; + if (spreadsheetKey) return spreadsheetKey; + + // Migration fallback for the two prototype releases. Saving again removes + // this unscoped value. + return userProperties.getProperty(OPA_KEY_PROPERTY); } function requireApiKey_() { @@ -105,18 +144,28 @@ function requireApiKey_() { function saveApiKey(apiKey) { const value = typeof apiKey === 'string' ? apiKey.trim() : ''; if (!value || value.length > 512) throw new Error('Enter a valid OilPriceAPI key.'); - const properties = documentProperties_(); - if (!properties) throw new Error('Open the add-on from a spreadsheet before saving a key.'); - properties.setProperty(OPA_KEY_PROPERTY, value); + const documentProperties = documentProperties_(); + const spreadsheetKeyProperty = spreadsheetKeyProperty_(); + if (!documentProperties || !spreadsheetKeyProperty) { + throw new Error('Open the add-on from a spreadsheet before saving a key.'); + } + documentProperties.setProperty(OPA_KEY_PROPERTY, value); + const userProperties = PropertiesService.getUserProperties(); + userProperties.setProperty(spreadsheetKeyProperty, value); + userProperties.deleteProperty(OPA_KEY_PROPERTY); return { success: true, configured: true }; } function deleteApiKey() { - const properties = documentProperties_(); - if (properties) { - properties.deleteProperty(OPA_KEY_PROPERTY); - properties.deleteProperty(OPA_ACTIVATED_PROPERTY); + const documentProperties = documentProperties_(); + if (documentProperties) { + documentProperties.deleteProperty(OPA_KEY_PROPERTY); + documentProperties.deleteProperty(OPA_ACTIVATED_PROPERTY); } + const userProperties = PropertiesService.getUserProperties(); + const spreadsheetKeyProperty = spreadsheetKeyProperty_(); + if (spreadsheetKeyProperty) userProperties.deleteProperty(spreadsheetKeyProperty); + userProperties.deleteProperty(OPA_KEY_PROPERTY); return { success: true, configured: false }; } @@ -153,18 +202,24 @@ function requestJson_(path, apiKey) { if (!normalizedPath.startsWith('/') || normalizedPath.includes('://') || normalizedPath.includes('..')) { throw new Error('Unsupported OilPriceAPI path.'); } - const response = UrlFetchApp.fetch(`${OPA_API_BASE_URL}${normalizedPath}`, { - method: 'get', - headers: { - Authorization: `Token ${apiKey}`, - Accept: 'application/json', - 'X-OilPriceAPI-Client': `${OPA_PRODUCT.activationHeader}/${OPA_VERSION}` - }, - muteHttpExceptions: true - }); + let response; + try { + response = UrlFetchApp.fetch(`${OPA_API_BASE_URL}${normalizedPath}`, { + method: 'get', + headers: { + Authorization: `Token ${apiKey}`, + Accept: 'application/json', + 'X-OilPriceAPI-Client': `${OPA_PRODUCT.activationHeader}/${OPA_VERSION}` + }, + muteHttpExceptions: true + }); + } catch (error) { + throw new Error('OilPriceAPI could not be reached. Check the connection and retry.'); + } const status = response.getResponseCode(); if (status === 401) throw new Error('The OilPriceAPI key is invalid or revoked. Replace it in the sidebar.'); if (status === 402 || status === 403) throw new Error('This dataset is not enabled for the account. Review OilPriceAPI pricing or use an entitled key.'); + if (status === 408) throw new Error('The OilPriceAPI request timed out. Retry in a moment.'); if (status === 429) throw new Error('The OilPriceAPI rate or quota limit was reached. Retry later or review the account limit.'); if (status < 200 || status >= 300) throw new Error(`OilPriceAPI returned HTTP ${status}. Retry later.`); let payload; @@ -184,14 +239,19 @@ function priceRecords_(payload) { else if (data && Array.isArray(data.prices)) records = data.prices; else if (data && data.prices && typeof data.prices === 'object') records = Object.values(data.prices); else if (data && typeof data === 'object' && ('price' in data || 'code' in data)) records = [data]; - const normalized = records.map((record) => ({ - code: String(record.code || '').toUpperCase(), - price: Number(record.price), - currency: String(record.currency || ''), - unit: String(record.unit || ''), - source: String(record.source || ''), - timestamp: String(record.created_at || record.as_of || record.timestamp || '') - })); + const normalized = records.map((record) => { + const rawPrice = record && record.price; + return { + code: String(record && record.code || '').toUpperCase(), + price: rawPrice === null || rawPrice === undefined || rawPrice === '' + ? NaN + : Number(rawPrice), + currency: String(record && record.currency || ''), + unit: String(record && record.unit || ''), + source: String(record && record.source || ''), + timestamp: String(record && (record.created_at || record.as_of || record.timestamp) || '') + }; + }); if (!normalized.length || normalized.some((record) => !record.code || !Number.isFinite(record.price))) { throw new Error('OilPriceAPI response is missing a finite market price.'); } @@ -225,7 +285,12 @@ function testConnection() { const probe = OPA_PRODUCT.allowedCodes.length ? latestPrices_([OPA_PRODUCT.allowedCodes[0]])[0] : productConnectionProbe_(); - return { success: true, code: probe.code || probe.contract || 'curve', timestamp: probe.timestamp || '' }; + return { + success: true, + code: probe.code || probe.contract || 'curve', + timestamp: probe.timestamp || '', + message: 'Connection and response schema verified.' + }; } function activateProduct_() { @@ -265,6 +330,9 @@ function marketDataRows_(records) { function calculateVoyageFuelCost_(seaDays, seaConsumption, portDays, portConsumption, vlsfoShare, vlsfoPrice, mgoPrice) { const values = [seaDays, seaConsumption, portDays, portConsumption, vlsfoShare, vlsfoPrice, mgoPrice].map(Number); if (!values.every(Number.isFinite)) throw new Error('Voyage-cost inputs must be finite numbers.'); + if ([values[0], values[1], values[2], values[3], values[5], values[6]].some((value) => value < 0)) { + throw new Error('Voyage days, consumption, and fuel prices must be non-negative.'); + } if (values[4] < 0 || values[4] > 1) throw new Error('VLSFO share must be between 0 and 1.'); const tonnes = (values[0] * values[1]) + (values[2] * values[3]); const blendedPrice = (values[4] * values[5]) + ((1 - values[4]) * values[6]); @@ -308,6 +376,7 @@ function buildBunkerVoyageWorkbook() { ['VLSFO share', 0.9, 'fraction'], ['Total fuel', singapore.tonnes, 'metric tonnes'] ]); + plan.getRange('B10').setFormula('=(B5*B6)+(B7*B8)'); plan.getRange('B9').setNumberFormat('0.0%'); const compare = sheet_('Scenario Compare'); @@ -318,6 +387,13 @@ function buildBunkerVoyageWorkbook() { ['Rotterdam', rotterdam.tonnes, rotterdam.blendedPrice, rotterdam.totalCost, rotterdam.totalCost - Math.min(singapore.totalCost, rotterdam.totalCost, houston.totalCost)], ['Houston', houston.tonnes, houston.blendedPrice, houston.totalCost, houston.totalCost - Math.min(singapore.totalCost, rotterdam.totalCost, houston.totalCost)] ]); + const scenarioFormulas = [5, 6, 7].map((row) => [ + `='Voyage Plan'!$B$10`, + `='Voyage Plan'!$B$9*SUMIFS('Port Prices'!$C:$C,'Port Prices'!$A:$A,A${row},'Port Prices'!$B:$B,"VLSFO")+(1-'Voyage Plan'!$B$9)*SUMIFS('Port Prices'!$C:$C,'Port Prices'!$A:$A,A${row},'Port Prices'!$B:$B,"MGO 0.5%")`, + `=B${row}*C${row}`, + `=D${row}-MIN($D$5:$D$7)` + ]); + compare.getRange(5, 2, 3, 4).setFormulas(scenarioFormulas); compare.getRange(5, 3, 3, 3).setNumberFormat('$#,##0.00'); activateProduct_(); diff --git a/portfolio/dist/bunker-voyage-planner/MARKETPLACE_LISTING.md b/portfolio/dist/bunker-voyage-planner/MARKETPLACE_LISTING.md index 802b66c..34c0c02 100644 --- a/portfolio/dist/bunker-voyage-planner/MARKETPLACE_LISTING.md +++ b/portfolio/dist/bunker-voyage-planner/MARKETPLACE_LISTING.md @@ -1,13 +1,17 @@ # Bunker Voyage Planner by OilPriceAPI — Marketplace listing -Status: release package validated locally. Do not claim Marketplace availability until Google approves and publishes this distinct listing. +Status: pre-submission package. Do not claim Marketplace availability until Google approves and publishes this distinct listing. ## App details - Application name: `Bunker Voyage Planner by OilPriceAPI` +- OAuth application name: `Bunker Voyage Planner by OilPriceAPI` - Category: Productivity - Pricing: Free of charge with paid features - Developer: `OilPriceAPI` +- Version: `1.0.0` +- Product guide: `https://www.oilpriceapi.com/integrations/bunker-voyage-planner` +- Pricing details: `https://www.oilpriceapi.com/pricing` Short description: @@ -21,7 +25,7 @@ Detailed description: > > The API key is stored in Apps Script document properties for the current spreadsheet. It is not written to cells, URLs, diagnostics, or browser-side HTML. Requests send only reviewed market identifiers and a product/version header used for first-party activation and reliability measurement. Spreadsheet contents, formulas, and cell values are not sent for analytics. > -> Google Sheets™ is a trademark of Google LLC. +> Google Sheets™ is a trademark of Google LLC. Bunker Voyage Planner by OilPriceAPI is not affiliated with or endorsed by Google LLC. ## Distinct workflow @@ -46,10 +50,26 @@ Detailed description: Google's mandatory `userinfo.email` and `userinfo.profile` defaults may appear in Cloud configuration. Product behavior does not use them. No Drive-wide scope is requested. +Combined Data Access justification: + +> This Editor add-on uses spreadsheets.currentonly only to create and format the named workbook tabs in the spreadsheet where the user explicitly runs Bunker Voyage Planner; it cannot browse or modify other spreadsheets. It uses script.external_request only to send the user-configured OilPriceAPI key and the product's reviewed market identifiers to api.oilpriceapi.com after the user selects Test connection or Build. It uses script.container.ui only to add the Bunker Voyage Planner menu and display its key-management sidebar, About dialog, build status, and recovery messages. No narrower scopes support these visible features. The add-on does not read Google account identity, browse Drive, send spreadsheet contents for analytics, or place API keys in cells or URLs. + ## Support links - Product guide: `https://www.oilpriceapi.com/integrations/bunker-voyage-planner` - Signup: `https://www.oilpriceapi.com/auth/signup?utm_source=workspace_marketplace&utm_medium=addon&utm_campaign=bunker_voyage_planner` +- Pricing: `https://www.oilpriceapi.com/pricing` - Privacy: `https://www.oilpriceapi.com/privacy/workspace-addons` - Terms: `https://www.oilpriceapi.com/terms/workspace-addons` - Support: `https://www.oilpriceapi.com/support` +- Setup: `https://www.oilpriceapi.com/integrations/bunker-voyage-planner` +- Help: `https://www.oilpriceapi.com/integrations/bunker-voyage-planner` +- Report issue: `https://www.oilpriceapi.com/support` + +## Submission assets + +- 32px icon: `assets/marketplace/bunker-voyage-planner/app-icon-32.png` +- 128px icon: `assets/marketplace/bunker-voyage-planner/app-icon-128.png` +- Card banner: `assets/marketplace/bunker-voyage-planner/card-banner-220x140.png` +- Screenshot: capture the exact immutable installed build at 1280×800 after the real-account smoke test; do not reuse another product's screenshot. +- OAuth demo video: record the exact OAuth consent screen, requested scopes, key configuration, connection test, and workbook build for this product. diff --git a/portfolio/dist/bunker-voyage-planner/REVIEWER_GUIDE.md b/portfolio/dist/bunker-voyage-planner/REVIEWER_GUIDE.md new file mode 100644 index 0000000..b69fc03 --- /dev/null +++ b/portfolio/dist/bunker-voyage-planner/REVIEWER_GUIDE.md @@ -0,0 +1,48 @@ +# Bunker Voyage Planner by OilPriceAPI — reviewer guide + +Status: pre-submission. Evidence that depends on a real installed Marketplace draft is explicitly gated below. + +## Reviewer prerequisites + +- Google Workspace host: Google Sheets +- Access model: the reviewer installs the Editor add-on and supplies an OilPriceAPI test key provided privately in the Marketplace review instructions. +- No Google account identity match is required. The OilPriceAPI key may belong to a different email address. +- The reviewer fixture must be synthetic, non-customer, active for the review window, and entitled to: `VLSFO_SGSIN_USD`, `MGO_05S_SGSIN_USD`, `VLSFO_NLRTM_USD`, `MGO_05S_NLRTM_USD`, `VLSFO_USHOU_USD`, `MGO_05S_USHOU_USD`. + +## End-to-end review flow + +1. Install the unpublished Marketplace draft in a blank spreadsheet. +2. In **Extensions → Add-ons → Manage add-ons**, select Bunker Voyage Planner by OilPriceAPI and choose **Use in this document**. +3. Refresh the spreadsheet once. +4. Open **Extensions → Bunker Voyage Planner → Configure OilPriceAPI key**. +5. Paste the private reviewer key and select **Save key**. +6. Select **Test connection** and confirm the green success result. +7. Select **Build workbook**. +8. Confirm these product-specific tabs exist: `Voyage Plan`, `Port Prices`, `Scenario Compare`. +9. Confirm source timestamps and units are visible and no API key is written to a cell. +10. Delete the stored key and confirm the sidebar reports that no key is configured. + +## OAuth scope demonstration + +- `spreadsheets.currentonly`: steps 7–9 create and format only the current spreadsheet. +- `script.external_request`: steps 6–7 request only the reviewed OilPriceAPI market data. +- `script.container.ui`: steps 2–7 display the add-on menu, sidebar, status, and recovery UI. + +## Evidence to attach + +- Apps Script ID: `1aQLBBWhyd_ffw1h9gmVFromI-5MwQsGd2v9REoUhXOHziDgbTA2eqY-u` +- Immutable Apps Script version: `2` +- Marketplace draft install: Pending the separate Cloud project and draft integration. +- Clean test spreadsheet URL: Provide privately after installed-draft smoke. +- OAuth demo video URL: Record after the exact OAuth branding and scopes are configured. +- Reviewed 1280×800 screenshot: Capture after installed-draft smoke. +- Reviewer test credential: Provide privately; never commit. + +## Expected recovery behavior + +- Missing key: asks the reviewer to configure a key or create an account. +- Invalid or revoked key: asks the reviewer to replace it in the sidebar. +- Dataset unavailable: explains the entitlement problem and points to pricing. +- Rate or quota limit: asks the reviewer to retry later or review the account limit. +- Timeout/network failure: asks the reviewer to check the connection and retry. +- Malformed or incomplete success response: rejects the data instead of building a misleading workbook. diff --git a/portfolio/dist/bunker-voyage-planner/SUBMISSION_CHECKLIST.md b/portfolio/dist/bunker-voyage-planner/SUBMISSION_CHECKLIST.md new file mode 100644 index 0000000..594c6f7 --- /dev/null +++ b/portfolio/dist/bunker-voyage-planner/SUBMISSION_CHECKLIST.md @@ -0,0 +1,40 @@ +# Bunker Voyage Planner by OilPriceAPI — pre-submission checklist + +Target Cloud project ID: `oilpriceapi-bunker-voyage` + +## Automated package — complete before deployment + +- [x] Distinct product title without a Google trademark +- [x] Product version `1.0.0` +- [x] Three least-privilege Apps Script scopes +- [x] First-party API fetch allowlist +- [x] Spreadsheet-scoped key storage with installed-custom-function compatibility +- [x] Product-specific activation header and UTM campaign +- [x] Trademark attribution and non-affiliation wording +- [x] Product-specific listing copy, reviewer guide, and graphic assets +- [x] Pricing, privacy, terms, setup, help, and support links +- [x] Unit, negative-path, claims, package, asset, and secret validation + +## Google account work — prepare, but do not submit yet + +- [x] Create or confirm the standalone Apps Script project +- [x] Push the exact validated package and create an immutable version +- [ ] Create the separate standard Google Cloud project `oilpriceapi-bunker-voyage` +- [ ] Enable Apps Script API and Google Workspace Marketplace SDK +- [ ] Link the Apps Script project to the standard Cloud project +- [ ] Configure External OAuth branding with the exact application name +- [ ] Add only the three manifest scopes and reconcile the Marketplace integration scopes +- [ ] Keep OAuth in Testing while preparing the installed draft +- [ ] Configure a Public Marketplace draft; remember visibility cannot be changed after saving +- [ ] Install the Marketplace draft, select **Use in this document**, refresh, and run the reviewer guide +- [ ] Inspect Apps Script executions for new exceptions, retries, or unexpected 4xx/5xx responses +- [ ] Capture a unique full-bleed 1280×800 screenshot from the immutable installed build +- [ ] Record the OAuth demonstration video for this exact app name and scope set +- [ ] Move OAuth to In production and prepare verification only when this product reaches its rollout slot + +## Hold point + +- [ ] Confirm the original OilPriceAPI listing has cleared OAuth and Marketplace review +- [ ] Apply any reviewer feedback to the shared runtime and every pending package +- [ ] Rebuild, create a new immutable version if anything changed, and repeat the real-account smoke +- [ ] Submit this listing only in the approved rollout order diff --git a/portfolio/dist/bunker-voyage-planner/Sidebar.html b/portfolio/dist/bunker-voyage-planner/Sidebar.html index 16b08ff..02b4007 100644 --- a/portfolio/dist/bunker-voyage-planner/Sidebar.html +++ b/portfolio/dist/bunker-voyage-planner/Sidebar.html @@ -7,6 +7,7 @@ h1 { color:#0f3557; font-size:20px; line-height:1.2; margin:0 0 6px; } .muted { color:#627d98; font-size:12px; } .card { background:#f0f7ff; border:1px solid #b8d8f0; border-radius:8px; margin:16px 0; padding:14px; } + .notice { background:#f8fafc; border-left:4px solid #0f6cbf; margin:16px 0; padding:10px 12px; } input { border:1px solid #9fb3c8; border-radius:5px; box-sizing:border-box; padding:10px; width:100%; } button { background:#0f6cbf; border:0; border-radius:5px; color:white; cursor:pointer; font-weight:700; margin-top:9px; padding:10px 12px; width:100%; } button.secondary { background:#526d82; } @@ -28,13 +29,23 @@

-
+
+
+ Data use +

+ Your key is stored for this spreadsheet in Apps Script properties and is never displayed after saving. + Only the key and reviewed market identifiers are sent to OilPriceAPI when you test or build. + Spreadsheet contents, formulas, cell values, and Google account identity are not sent for analytics. + Your OilPriceAPI account email does not need to match your Google account. +

+
+

Create an API key · Product guide

- Product requests include a product/version HTTP header so OilPriceAPI can measure activation and reliability. - No spreadsheet contents, formulas, or cell values are sent for analytics. + Privacy · + Terms · + Support

-

Create an API key · Product guide

-

Google Sheets™ is a trademark of Google LLC.

+

Version · Google Sheets™ is a trademark of Google LLC. Not affiliated with or endorsed by Google LLC.