Nakodo uses YouTube API Services, so before asking for an audited quota I read the Developer Policies against our own public pages instead of against our code. The code was mostly fine. The pages were not, and the diff that fixed them touched a footer, a pricing table, a privacy notice, a terms page and one word in a sign-up checkbox. Seven files, thirty insertions.
That is the part of compliance work nobody writes about, so here it is, with the live pages to check it against.
The http link that stays http
Our privacy notice already linked Google's privacy policy. It linked it like this:
<Ext href="https://policies.google.com/privacy">Google Privacy Policy</Ext>
Which is the current URL for that document, over https, and is wrong anyway. The policy text says:
The privacy policy must: ... reference and link to the Google Privacy Policy at http://www.google.com/policies/privacy
That URL is http, and it redirects once:
$ curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://www.google.com/policies/privacy
301 -> http://www.google.com/policies/privacy/
So the sensible engineering instinct, which is to link the canonical https URL and let the redirect die, is the wrong instinct here. A requirement that names a URL is satisfied by that URL. Whoever checks the page is looking for the string the policy gave them, not for a document that is reachable by some equivalent route, and you do not get to argue the point in a review queue. Both the privacy notice and the terms now carry it exactly as written, and you can grep the live HTML for http://www.google.com/policies/privacy and find it in each.
I left a comment nowhere near it, which was a mistake I am fixing by writing this paragraph: the next person to run a link checker over the site will see one http link among a hundred https ones and "fix" it.
Read is not agree
The sign-up checkbox used to say you accept the Terms and have read the Privacy notice. One word, now:
- and have read the{" "}
+ and the{" "}
So it reads: you agree to the Terms and the Privacy notice. The policy asks that every API client "require users to agree to a privacy policy before users can access the API Client's features and functionality", and reading a document is not agreeing to it. That distinction is older than any API policy and we had got it wrong in the direction that sounds more polite.
The logo cannot go in the sentence
The footer now carries an attribution, which you can see at the bottom of every public page, starting with nakodo.app:
<div className="flex items-center gap-1">
<span>Creator search uses YouTube API Services</span>
<YouTubeLink href="https://www.youtube.com" label="YouTube" variant="logo" />
</div>
The sentence and the logo are siblings, not one run of inline content, and that is a branding rule rather than a layout preference: the logo is not allowed to sit inside a sentence as if it were a word. The other rules in the same guidelines are size (at least 20px tall), clear space around it (at least the width of the triangle in the icon), and never being the most prominent thing on the screen. A footer solves the third one by existing. The first two are the h-5 on the SVG and the p-2.5 on the link.
The component is src/components/brand/youtube.tsx, and the interesting thing about it is that it is the one file in the app allowed to ignore our colour system:
<path fill="#FF0000" d="M154.3,17.5c-1.8-6.7-7.1-12-13.8-13.8..." />
<polygon fill="#FFFFFF" points="64.2,78.4 104.6,55 64.2,31.6" />
Hex literals, in a codebase where every other colour is a token. The paths are YouTube's own SVG, unmodified, so the red is their red. The wordmark is the one concession to dark mode, className="fill-[#282828] dark:fill-white", because the guidelines give a light background colour and a dark background colour and let you pick by background.
Accessibility falls out of the same shape. The SVG is aria-hidden="true" with focusable="false", and the accessible name lives on the link:
<a href={href} target="_blank" rel="noopener noreferrer" aria-label={label} title={label}>
A logo is a picture of a word. If both the link and the image carry the name, a screen reader says it twice, and an image that is already labelled by its link should keep quiet. The label prop is written for the place it is used: in the footer it is just "YouTube", while beside a creator it is the sentence that says whose channel it opens.
The row that had to come out of the pricing table
This one is a product change dressed as a compliance change:
- {
- label: "YouTube searches a day",
- value: (p) => (p.limits.platforms.includes("youtube") ? n(p.limits.searchesPerDay) : <No>None</No>),
- },
Our pricing page is a table generated from the plan objects, one row per limit, and that row published a number of API calls per plan. Selling plans by how much of a provider's quota each one includes is not how any of this is meant to work, and it is also a bad row: nobody buying creator outreach wants to reason about search calls. "Platforms searched" survives, because that is the same information in the units the buyer actually has, and the terms page lost a matching phrase in the same spirit:
- The Free plan costs nothing. Paid plans contact more creators, add YouTube and business campaigns, and include
+ The Free plan costs nothing. Paid plans contact more creators, search more platforms, add business campaigns,
Both legal pages are arrays of typed sections rendered by one component, so these were small diffs in a data file rather than edits to markup, and the anchors the sections generate did not move. Keeping legal copy as data is boring right up to the day something external tells you to change the same sentence in two places.
Deletion, and whose copy it is
The privacy notice already promised to delete what we hold about a creator who objects. It now says when, and then says something that is easy to leave out:
That deletes our copy only: it doesn't affect anything stored by YouTube, and to delete data on YouTube itself, use YouTube.
The first half is a promise with a number in it (seven days). The second half is a promise about what we cannot do, and it is there because the alternative is a creator believing that mailing us removes something from their channel. The terms page has carried the other half of this for a while: YouTube's rules mean the data gets refreshed or deleted within 30 days either way, which is the one compliance rule that genuinely changed our schema rather than our copy.
Four of the five changes above were words. None of them took an hour. All of them were invisible to every test we have, because no test we have reads the page the way a reviewer does, and the only reason they got made at all is that I read the policy as a spec with acceptance criteria and then went looking for the places our pages failed it. If you ship on someone else's API, that is a reading worth doing before someone else does it for you. The product's own description of what it reads and keeps is on how it works, and the complete version, including the 30 day rule, is in the privacy notice.
Top comments (0)