Skip to content

feat(docs): getting started for agents - #29

Merged
nour-karoui merged 17 commits into
mainfrom
chore/getting-started-for-agents
Sep 7, 2026
Merged

nour-karoui merged 17 commits into
mainfrom
chore/getting-started-for-agents

Conversation

@nour-karoui

@nour-karoui nour-karoui commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Self-service Getting Started for agents & humans on the Labs API — IP-3028.

Makes the API Reference self-service: a reader — or an agent — with only the docs URL and a mol_ credential can get to a lab with files in it.

What's new

  • api-reference/getting-started/ — "choose your path" for the four lanes, prerequisites with real costs, a ≤10-minute quickstart, and four tutorials: public upload, encrypted upload, agent-as-Contributor on a lab made in the app, announce. Plus an agent one-pager meant to be pasted into a system prompt.
  • Publishes what used to be "contact the team": both x402 gateway base URLs, the staging-introspection route to the schema, the USDC faucet. Only the credential request remains.
  • Removes the Assignment Agreement from all of api-reference/; legal-agreements.md is hidden and out of nav.
  • Corrects the sign-in flow for the IP-3013 single-use nonce, the Authorization: Bearer contradictions, the "service tokens are lab-scoped" error, broken anchors, and the error contract.
  • Adds a table comparing the three wallets an integration juggles — owner, agent, and the Lab's OCL account — including which API field each address belongs in (requested by Logan).
  • Deletes api-reference/IPNFT-api.md, an orphan duplicate teaching the retired x-api-key.

Verified against staging

All four tutorials run end to end; 120/120 embedded GraphQL documents validate against the live schema; 0 broken links. Evidence: lab lab-1542, [mint tx]((https://sepolia.basescan.org/tx/0x65c63576e1ccbf90e86de9f21ea2785a865fba7b64b99e48699e2fb422635c82), public datasetId did:odf:fed01750c84c6aba2f13fda72de412cb858d034b3b2dc76a388f5e58fbe09c7a11474, encrypted did:odf:fed01e35ce981a01fdfc57d037252bdf3bcf2775e0bb47b0a8222d851d3d0f735cacf with a verified decrypt round trip. Tutorial 3 verified post-IP-3013 on testing-lab: an agent wallet granted Contributor in the app self-issued a token and uploaded into a lab it does not own.

nour-karoui and others added 8 commits August 27, 2026 17:11
getServiceSignInMessage is no longer deterministic: the message embeds a
server-issued single-use nonce and its expiry, so a signature over it can
neither be replayed nor cached. Verified against staging.

- authentication.md dropped the "deterministic message" claim and now states
  the single-use rule, the 10-minute window and last-write-wins per
  (wallet, serviceName).
- service-tokens.md selects the new `expiresAt`, documents the field, and
  carries the failure table keyed on details.reason: NONCE_NOT_FOUND
  (never requested, or already consumed), NONCE_EXPIRED, INVALID_SIGNATURE
  (altered text, or superseded by a later fetch) and WALLET_MISMATCH.
  Superseded returns INVALID_SIGNATURE, not NONCE_NOT_FOUND -- the record
  still exists, it just holds a newer nonce.
- changelog.md announces the change with a migration note, and fixes an
  IPNFT agreements snippet that omitted the required `id` argument.

Confirmed on staging: window measured at 10.02 min, replay rejected,
supersession rejected, re-worded message rejected, expiry rejected
(9/9 checks).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tutorial 1 failed outright on 1 of 5 runs against staging:
initiateCreateOrUpdateFile returned NOT_FOUND ("Project 0x... does not
exist") immediately after createLab had returned success.

Cause is an asymmetry, not a transient: onchain_lab rows are written by the
event indexer that ingests the mint, and createLab tolerates their absence
via its ownerOf fallback (auth-service.ts:615) while the content writes read
the indexed record directly (auth-service.ts:283-300). So createLab can
succeed on a lab that is not yet writable.

- withIndexerLagRetry now lives in the shared setup block, with both lag
  cases named: NOT_FOUND after a mint, UNAUTHORIZED after a role grant.
- Tutorial 1 Step 4 wraps its first call in it, in the snippet and the
  complete script, and warns that Step 3 succeeding is no guarantee.
- Tutorial 3's withRoleGrantRetry is renamed to the same helper with
  codes: ["UNAUTHORIZED", "NOT_FOUND"], so both tutorials teach one idiom.
- for-agents.md gains the rule as #8.
- Both upload tutorials document `UPSTREAM_UNAVAILABLE: "Path is occupied"`
  as NOT retryable despite the code -- a re-run against the same lab. Fix
  is a new `path`, or `ref` to add a version.
- Tutorial 1/3 Step 1 also carry the nonce rules from the previous commit,
  since they share those regions.
- Tutorial 3 no longer implies isAgent must be true; a grant with
  isAgent=false authorized an upload on staging exactly the same.

Verified: helper unit-tested as published (5/5), Tutorial 1 three clean
runs, full T1->T2->T4 chain on lab-1542, Tutorial 3 clean on testing-lab.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…asks

Follow-ups from the IP-3028 audit, each verified against staging.

- browse-and-search.md: three public-endpoint callouts claimed the consumer
  credential goes in as `Authorization: Bearer`. It does not -- the same
  error already fixed in lab-management.md.
- tokenization-api.md: the 401 row said to check `Authorization: Bearer`,
  contradicting the same page's own header section. Its two "contact the
  Molecule team" asks now point at the published Contracts reference for
  ABIs/addresses and at the single credential-request template for the rest.
- ipnft-api-deprecated.md: the four-step credential ask collapses to that
  same template.
- files.md: updateFileMetadata's parameter table omitted the required
  accessLevel, and `ref` now says explicitly that it is the datasetId, not
  the file path.
- docs-sync.md: restore the api-reference/authentication.md row, dropped
  when the getting-started rows were added, and point service-tokens.md at
  the sign-in nonce constants and reason codes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two decisions from the IP-3028 review.

1. `error.details` is doubly encoded in-band today (toGraphqlApiError
   stringifies into an AWSJSON field and AppSync serializes it again), so the
   documented single `JSON.parse(...).reason` returned `undefined` silently and
   any agent branching on `reason` took the wrong path. Rather than wait on the
   backend fix, the docs now teach a `parseDetails` helper that loops until the
   value is no longer a string: correct for a plain object (thrown queries), a
   single-encoded string, and today's double-encoded string alike, so it needs
   no change once the encoding is corrected. Applied to the shared setup block,
   all four tutorial scripts' assertOk, the Labs API error-handling reference,
   the agent one-pager, the changelog migration snippet, lab-management and
   legal-agreements. Verified 9/9 including against a live staging error.

2. Deleted `api-reference/IPNFT-api.md` — an orphan duplicate of
   `ipnft-api-deprecated.md`, never in SUMMARY.md, still teaching the retired
   `x-api-key` header, and holder of the last "contact the Molecule team" in
   api-reference/. Nothing linked to it. docs-sync.md records the removal.

Also widens the indexer-lag retry budget on measured evidence: the earlier
5 attempts / ~30s was sized on a lag of seconds, but a mint on staging took
over four minutes to index (probed: NOT_FOUND for 211s straight, writable
later). The helper is now 12 attempts with backoff capped at 30s (~4 min) and
logs each wait, and the prose no longer claims it "clears in seconds".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Requested by Logan, for the PeptAI/Martin shape: an owner wallet, an agent
wallet and the Lab's own OCL account are all in play at once, each documented
in a different place and never side by side. A reader assembling the picture
had to visit onchain-lab.md, roles-and-permissions.md, authentication.md and
Tutorial 3.

Adds one table to authentication.md — what each wallet is, who holds the key,
how it gets its rights, how it authenticates, **which field its address goes
in**, and what it cannot do — plus the two failure modes it prevents:

- Passing the owner's address where `labAccountAddress` belongs in
  accessControlConditions. Evaluation fails closed, so the upload succeeds and
  then nobody can decrypt, with no error explaining why.
- Expecting the agent to inherit the human's reach; its rights come from its
  own grant, which is why a few mutations stay Owner-only.

Also documents that `oclId` is not a wallet address: it packs the version,
namespace, LabNFT tokenId and the OCL account address, so its trailing 40 hex
chars are `labAccountAddress`. Layout taken from
`lambda/common/utils/ocl-id.ts` and verified by decoding three live staging
labs (testing-lab/1526, lab-1533, lab-1546) — each decoded tokenId and address
matches the values the API returns. That is also why a zeroed oclId is
rejected with "embedded address is zero" rather than a not-found.

Linked from Tutorial 3, Tutorial 2's conditions step, the agent one-pager
(prose plus a new rule 9) and roles-and-permissions.md, so the scattered
partial explanations become pointers to one canonical table.

Not asserted: whether PeptAI's owner wallet is specifically a Privy embedded
wallet. The table says "typically a Privy embedded wallet created at email
sign-in, but any wallet that holds the LabNFT" — worth confirming with Martin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LAB_PAGE https://testnet.labs.molecule.xyz/projects/<shortname>
```

Production: `https://production.graphql.api.molecule.xyz/graphql`, `base` (8453), factory `0xECdF4f05384056507485C90aeAb0a83268760D6E`, LabNFT `0x9F96027eeAFb9ad5F2b5d7043B36Ee96B2EeBE92`, AccessResolver `0x89a14Be8f7824d4775053Edad0f2fA2d6767b72B`, condition chain `"base"`, lab page `https://labs.molecule.xyz/projects/<shortname>`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could format this also as a code block, similar to staging.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As announcements are somewhat deprecated or at least not shown anymore, I guess we should not highlight it anymore.

Comment on lines +140 to +151
## Optional — announce it

```graphql
mutation CreateAnnouncement($oclId: String!, $headline: String!, $body: String!, $attachments: [String!]) {
createAnnouncement(oclId: $oclId, headline: $headline, body: $body, attachments: $attachments) {
message
error { code message requestId retryable details }
}
}
```

`attachments` takes the `datasetId` values returned by `finishCreateOrUpdateFile`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, I think we shouldn't mention announcements any longer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could just remove this page to keep our docs slim. It was published recently as a quick explanation for an agentic presentation by the product team and shouldn't have a big audience.

nour-karoui and others added 3 commits September 1, 2026 14:25
Announcements are no longer surfaced in the Molecule app, so they are
out of every onboarding, how-to and feature-description surface:

- delete the Announce tutorial and the "Create Announcement" section
  in labs-api/files.md
- drop the createAnnouncement x402 endpoint, and re-point the 402
  challenge and request-format worked examples at createLab
- drop the permission-matrix rows in authentication.md and
  roles-and-permissions.md, and the Contributor gate lists
- strip the prose in the data/roles deep dives, the developer guide
  and the Molecule Skill page (Announce phase removed, steps renumbered)

Kept deliberately: the LabEventAnnouncement / SearchLabsAnnouncementHit
union variants and the ANNOUNCEMENT filter value, which callers still
receive from labActivity and searchLabs regardless of what we document;
the changelog and release-notes entries, which are shipped history; and
the verbatim aura-orchestrator SKILL.md frontmatter quote, which is a
reproduction of the plugin's own manifest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The feature is no longer supported, so the schema-shape and historical
references kept in 4e02a6d are gone too:

- browse-and-search.md: the LabEventAnnouncement and
  SearchLabsAnnouncementHit union variants, the announcement-only
  labActivity curl example (now queries LabEventFileAdded), the
  attachments breaking-change note, the use-case list and the
  __typename switch in the JS example
- authentication.md: the ANNOUNCEMENT filter values on labActivity
  and activities
- changelog.md: the createAnnouncement gate-list entry (six writes ->
  five), the renamed-query and renamed-mutation rows, and the diff
  example (now finishCreateOrUpdateFile)
- release-notes/labs-api.md: createAnnouncement in the write list
- molecule-skill.md: the aura-orchestrator SKILL.md quote is now
  abridged and labelled as such, rather than quoting a stale manifest

Also reworded two uses of the English verb "announced" (new error codes
are "published" in the changelog) so the term does not appear at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread api-reference/getting-started/README.md Outdated
| **I have no credential, or I want to pay per call** instead of holding a long-lived token | **x402 gateway** — settle USDC on Base per request, no service token to provision | [x402 Gateway](../x402-gateway.md) |
| **I already made my lab in the app** (email sign-in, no wallet) **and now I want my agent writing into it** | **Agent-as-Contributor** — the human grants a role, the agent issues its own token | [Tutorial 3](tutorial-3-agent-access.md) |

The lanes compose. A common shape is the plugin lane for the workflow plus x402 for the paid mutations, which is exactly what the plugin does by default.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this reads well and is slightly confusing. I would change this to:

"The lanes are composable, so choosing one now doesn't lock you in to that lane only."

Comment thread api-reference/getting-started/README.md Outdated

### 1. A `mol_` consumer credential — the one manual step

Every request to the API carries a consumer credential in the `Authorization` header. There is no self-service issuance yet, so this is the single "ask the team" left in the API docs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second part of this line reads more like a review of the docs rather than an instruction. Change:

"There is no self-service issuance yet, so this is the single "ask the team" left in the API docs."

To:

"There is no self-service issuance yet (coming soon), so you will need to request this from the Molecule team."

Comment thread api-reference/getting-started/README.md Outdated
| Your situation | Lane | Start here |
| -------------- | ---- | ---------- |
| **I run an AI coding agent** (Claude Code, Codex, Cursor) and want it to do the whole workflow | **Molecule Skill plugin** — a skill + MCP server that wraps every network, onchain and crypto operation as one tool call | [Molecule Skill](../../ai-tooling/molecule-skill.md) |
| **I'm scripting against the API** in Node/TypeScript and want to see the raw calls | **Raw GraphQL + viem** — self-issue a service token, mint, upload | [Tutorial 1](tutorial-1-public-upload.md) |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change:

"| [Tutorial 1] ..."

To:

"| [Public File Upload Tutorial] ..."

Comment thread api-reference/getting-started/README.md Outdated
| **I run an AI coding agent** (Claude Code, Codex, Cursor) and want it to do the whole workflow | **Molecule Skill plugin** — a skill + MCP server that wraps every network, onchain and crypto operation as one tool call | [Molecule Skill](../../ai-tooling/molecule-skill.md) |
| **I'm scripting against the API** in Node/TypeScript and want to see the raw calls | **Raw GraphQL + viem** — self-issue a service token, mint, upload | [Tutorial 1](tutorial-1-public-upload.md) |
| **I have no credential, or I want to pay per call** instead of holding a long-lived token | **x402 gateway** — settle USDC on Base per request, no service token to provision | [x402 Gateway](../x402-gateway.md) |
| **I already made my lab in the app** (email sign-in, no wallet) **and now I want my agent writing into it** | **Agent-as-Contributor** — the human grants a role, the agent issues its own token | [Tutorial 3](tutorial-3-agent-access.md) |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change:

"| [Tutorial 3] ..."

To:

"| [Agent Access Tutorial] ..."

Comment thread api-reference/getting-started/README.md Outdated

| Item | Cost | How we know |
| ---- | ---- | ----------- |
| **LabNFT mint** | Gas only. `mintFeeWei()` reads **0** on Base Sepolia **and** on Base mainnet (verified 2026-08-27 by `eth_call`) | Read it live yourself — the tutorials do, and send it as `value` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove "verified 2026-08-27 by eth_call"

Change to:

"""
| LabNFT mint | Gas only on Base Sepolia and on Base mainnet | Read it live yourself using mintFeeWei(), and if non-zero, send it as value |
"""

Comment thread api-reference/getting-started/README.md Outdated
| ---- | ---- | ----------- |
| **LabNFT mint** | Gas only. `mintFeeWei()` reads **0** on Base Sepolia **and** on Base mainnet (verified 2026-08-27 by `eth_call`) | Read it live yourself — the tutorials do, and send it as `value` |
| **`createLab`, uploads and other content writes** (service-token lane) | Free | Consumer credential + self-issued service token |
| **The same mutations via x402** | Quoted per request in the `402` challenge — **$0.01 USDC** on both environments today | [Read the price off the challenge](../x402-gateway.md#reading-the-402-challenge); never hardcode it |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change:

"Quoted per request in the 402 challenge — $0.01 USDC on both environments today"

To:

"Quoted per request in the 402 challenge, currently $0.01 USDC on both environments "

Comment thread api-reference/getting-started/README.md Outdated
```

{% hint style="warning" %}
**Two places the indexer trails, and both need that retry.** A successful response does not mean every downstream read is caught up yet:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unclear and poorly written, improve the wording:

eg:

"Onchain and offchain systems that work in parallel must be synchronised, and therefore there can be delays in data propagation. This means that a successful response does not mean that every downstream read has caught up yet"

Comment thread api-reference/getting-started/README.md
Comment thread api-reference/getting-started/README.md Outdated

***

## Shared setup

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this section and why is it here? It is unclear why this is relevant to the reader. It's important to imagine that the reader has zero knowledge of how Mol API works or why they are doing each action

@nour-karoui
nour-karoui requested a review from ph0ph0 September 3, 2026 09:00

@ph0ph0 ph0ph0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review from my agent - please ask your agent to double check that they are valid and if so, fix them:

Issue 1 — WALLET_MISMATCH documented as a sign-in failure the resolver can never return (high)

Where:

  • api-reference/getting-started/create-lab-and-upload-file.md:103
  • api-reference/labs-api/service-tokens.md:111
  • api-reference/changelog.md:22
  • .github/prompts/docs-sync.md:68

Issue: All four state that generateServiceToken returns UNAUTHENTICATED / reason: WALLET_MISMATCH when "walletAddress isn't the signer". The resolver (desci-infra
lambda/appsync-resolver-labs-lambda/index.ts, signature path) emits exactly three reasons: NONCE_NOT_FOUND, NONCE_EXPIRED, INVALID_SIGNATURE. A wrong walletAddress recomposes the
sign-in message for the claimed address, verifyMessage fails, and the caller gets INVALID_SIGNATURE. WALLET_MISMATCH exists only in the legal-agreement resolvers' Privy-user
path (x-wallet-address header vs. the wallet named in the call). The commit's own "9/9 staging checks" never probed wallet-mismatch — because it's unreachable.

Why it matters: An agent that hits the wrong-address case gets INVALID_SIGNATURE, whose documented fix ("sign the most recent message byte-for-byte") never says to check the
address — while the branch that would have can never fire. And docs-sync.md:68 instructs the sync agent that these reasons "come from the generateServiceToken resolver", baking
the error into the source-of-truth map.

Fix: Delete the WALLET_MISMATCH rows from the three sign-in tables; fold the case into INVALID_SIGNATURE ("altered text, a superseded message, or a walletAddress that is not the
signer"). Drop it from docs-sync.md:68's list. Keep changelog.md:125's general catalogue row — the reason genuinely exists on the legal-agreement path.


Issue 2 — Bad expiresIn documented as VALIDATION_FAILED; it actually returns a retryable-looking INTERNAL_ERROR (medium-high)

Where:

  • api-reference/getting-started/create-lab-and-upload-file.md:104
  • api-reference/labs-api/service-tokens.md:100 and :113
  • api-reference/authentication.md:154

Issue: expiresIn is never validated in the resolver — it flows into token-manager-service.generateServiceToken, where calculateExpirationDate (lib/utils/token-expiration.ts)
throws a plain Error on bad format or out-of-bounds values. The service's own catch wraps it with maskedErrorResult(..., { reason: "TOKEN_GENERATION_FAILED" }), and
classifyError's generic-Error fallback yields INTERNAL_ERROR with a masked message. So "3y" comes back as INTERNAL_ERROR / reason: TOKEN_GENERATION_FAILED — not
VALIDATION_FAILED.

Why it matters: The agent one-pager (correctly) classifies INTERNAL_ERROR as retryable, so an agent following these docs will retry a permanently-failing request with backoff
instead of fixing its input — the exact failure loop the error contract exists to prevent. It also means "our fault, file a bug" masking is hiding a client mistake — arguably a
backend bug worth its own ticket (validateExpirationFormat exists in the backend and is unused in this path).

Fix: Either fix the backend first (pre-validate expiresIn in the resolver) and keep the docs as-written, or correct the four doc sites to today's behavior. Given the webinar
deadline: correct the docs now ("today this surfaces as INTERNAL_ERROR / reason: TOKEN_GENERATION_FAILED; validate the format client-side") and file the backend ticket.


Issue 3 — reason: NOT_CONTRIBUTOR doesn't exist (medium)

Where: api-reference/getting-started/agent-as-a-lab-contributor.md:136

Issue: The indexer-lag hint says a pre-index write returns UNAUTHORIZED (reason: NOT_CONTRIBUTOR). That reason string appears nowhere in the backend — a membership denial is
code: UNAUTHORIZED with details.reason: "UNAUTHORIZED" (auth-service.ts's MembershipAuthReason map → membershipDenialApiError). The retry helper matches on the code, so the
script works; only the parenthetical is fiction — but it's exactly the kind of specific-looking token an agent will grep responses for. (Note: changelog.md:126 also lists
NOT_CONTRIBUTOR as an example reason under UNAUTHORIZED — same correction applies there.)


Issue 4 — example-workflow.md deleted with no redirect; the PR built the right stub and then deleted it (medium)

Where: commit 7b50197 deleted api-reference/labs-api/example-workflow.md; the repo still has no .gitbook.yaml at head 4974fda.

Issue: Example Workflow has been the published, SUMMARY-listed tutorial URL for months — the page external bookmarks, chat links and search results point at. An earlier commit in
this PR replaced it with a hidden "This page moved" stub (the correct GitBook pattern); a later commit deleted the stub, so the live URL will 404 after merge. GitBook Git-sync
does not auto-redirect removed pages. (IPNFT-api.md's deletion is fine — never in SUMMARY, so no published URL.)

Fix: Restore the 20-line hidden stub (now pointing at the renamed tutorial files), or add a .gitbook.yaml with redirects: { api-reference/labs-api/example-workflow:
api-reference/getting-started/README.md }.


Issue 5 — The oclId diagram is 6 hex chars short (low)

Where: api-reference/authentication.md:195

Issue: tokenId is an 80-bit field (ocl-id.ts: (id >> 160n) & ((1n << 80n) - 1n)), i.e. 20 hex chars. The diagram shows a 14-char field, so the pictured "oclId" is 58 hex chars —
an invalid 29-byte value on a page whose whole point is byte-level precision. The decoded values (1526, the trailing address) are right.

Pasteable comment:

▎ The tokenId field in this diagram is 14 hex chars, but it's an 80-bit field (ocl-id.ts: (id >> 160n) & ((1n << 80n) - 1n)) — 20 hex chars, making the full oclId 64. As drawn
▎ the example is a 58-char/29-byte value that validateOclId would reject. Should read 000000000000000005f6 for 1526.


Issue 6 — The announcements purge documents an API smaller than the live one, with no deprecation notice anywhere (medium-high, new in the last six commits)

Where:

  • api-reference/labs-api/browse-and-search.md — the labActivity, activities, and searchLabs sections
  • api-reference/x402-gateway.md:52-59 — endpoints table vs. the "single source of truth" sentence
  • The absence: "announcement" now appears zero times across api-reference/ and release-notes/

Issue: I checked the current dev schema and gateway: createAnnouncement is still a live mutation, LabEventAnnouncement is still in the LabActivityNode union,
SearchLabsAnnouncementHit is still in SearchLabsHit, the ANNOUNCEMENT filter enum value still exists, and X402_WRITE_MUTATIONS still allow-lists createAnnouncement. Three
consequences:

  • labActivity is now described as "the file-event timeline", but unfiltered it still returns announcement nodes — including ones this very PR's Tutorial 4 verification created on
    staging. The docs simultaneously deleted the filter parameter documentation, which was the actual mechanism for getting the file-only feed the page now claims is the default.
    Same for searchLabs, whose union is documented with one arm out of two — agents doing exhaustive union handling will hit typenames the docs never mention.
  • x402-gateway.md:59 still says "The allow-list above is the single source of truth in X402_WRITE_MUTATIONS" — directly above a table that now silently omits one of that
    constant's six entries. The page contradicts itself.
  • The previous commit's promise ("retained as a reference for integrations that already use it") was reversed: tutorial-4 deleted, files.md's Create Announcement section deleted
    — so a mutation that still works, and that these docs taught until this PR, now has zero documentation and zero deprecation notice, while the changelog's error-contract section
    promises changes are "published on this page".

Fix: Distinguish "stop recommending" from "deny existence": one deprecation entry in the changelog, restore the filter parameter row on browse-and-search with deprecation
framing, and either mark the x402 table row deprecated or file the desci-infra change removing createAnnouncement from X402_WRITE_MUTATIONS so the "single source of truth"
sentence becomes true again.


Issue 7 — Dated historical records were rewritten to be false (medium, new)

Where:

  • api-reference/changelog.md:38 — the IP-3013 role-parity entry
  • api-reference/changelog.md ~190-198 — the V2-rename/migration tables
  • release-notes/labs-api.md:17-20 — entry dated 2026-08-25

Issue: The role-parity entry now reads "The five content-write mutations" — the shipped change covered six, and createAnnouncement still gates a service token on Contributor
today (verified against the resolver gates). The V2 tables dropped the createAnnouncementV2 → createAnnouncement and projectAnnouncementsV2 → labActivity/activities (filter:
ANNOUNCEMENT) rows — a legacy integrator's only documented migration path. And a release note explicitly dated 2026-08-25 was edited to remove createAnnouncement from the list of
mutations the Assignment Agreement gate applied to, which it did. A changelog that retroactively changes what shipped is worse than one mentioning a deprecated feature — it's
the reference integrators check when old code breaks.

Fix: Restore the accurate lists and rows, annotated "(announcements since deprecated — see [entry])" rather than deleted. History entries state what was true at their date.


Issue 8 — docs-sync.md's README row describes date stamps that no longer exist (low, new)

Where: .github/prompts/docs-sync.md:56 vs. the refactored api-reference/getting-started/README.md cost table (~line 100).

Issue: The docs-sync row says the README's live values (mintFeeWei() readings, x402 prices) are "each stamped with the date it was checked" — the refactor removed those stamps
("verified 2026-08-27 by eth_call" and the price's as-of date are gone). The stamps were also the guard that row relies on ("never update one of those from a diff alone").

Pasteable comment:

▎ Small consistency catch: docs-sync.md's README row still says the live values here are "each stamped with the date it was checked", but this refactor dropped the stamps from
▎ the cost table (mintFeeWei() no longer says "verified 2026-08-27 by eth_call", the $0.01 lost its as-of date). Either restore the dates — they're also what makes "flag, don't
▎ edit from a diff" enforceable — or update the docs-sync row.


Comment thread api-reference/getting-started/README.md Outdated

Every request to the API carries a consumer credential in the `Authorization` header. There is no self-service issuance yet (coming soon), so you will need to request this from the Molecule team.

Request it on the [Molecule Discord](https://t.co/L0VEiy4Bjk) with this template:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a general comment and not something i would like you to change right now as it will require an internal decision. When we have agreed with Kevin/Ella, we can create a new PR for this:

We don't mention which channel to post the request in, and the request doesn't tag anyone on Discord - this will probs cause requests to go unnoticed...

We need to discuss this internally, perhaps we have a dedicated channel or at least someone who should be tagged in the request so we don't miss any.

We should have self-service ready by EOY, so hopefully we won't need this for long.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will mention this to ella.

Comment thread api-reference/getting-started/README.md Outdated
| Staging | `https://testnet.labs.molecule.xyz/projects/<shortname>` |
| Production | `https://labs.molecule.xyz/projects/<shortname>` |

`shortname` is derived server-side from the lab's name and is `null` until that has happened, so a freshly minted lab is reachable by `oclId` before it is reachable by slug.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect, a Lab page is never reachable by appending the oclId.

Before the user renames the lab, the url should be appended with lab-<tokenId>. Once the lab has been renamed, lab-<tokenId> will stop resolving.

}
```

`shortname` is derived server-side from the lab's name and is `null` until it has been derived — that is expected on a lab that has just been minted and not yet named.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment in Getting Started. This isn't true as it defaults to lab-<tokenId>


That is exactly what happens here. `createLab` can succeed before your mint has been indexed, because it falls back to checking ownership onchain. The file mutations have no such fallback — they read the indexed record, and return `NOT_FOUND` until it arrives.

So wrap the first call in [`withIndexerLagRetry`](shared-setup.md). On staging the record usually appears within seconds, but one mint took **over four minutes**, which is why the helper keeps retrying that long instead of giving up after a few seconds.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove the "...but one mint took over four minutes..."

Change this:

"So wrap the first call in withIndexerLagRetry. On staging the record usually appears within seconds, but one mint took over four minutes, which is why the helper keeps retrying that long instead of giving up after a few seconds."

To something like:

"Wrap the first call in withIndexerLagRetry so the helper will retry until the record arrives."

@nour-karoui
nour-karoui requested a review from ph0ph0 September 3, 2026 19:31

@ph0ph0 ph0ph0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please merge once changes are made, and ensure that all my previous comments are resolved as well :)

}
```

`shortname` is `null` here, which is expected on a lab that has just been minted and not yet named — it is derived server-side once the lab is given one. Note `labNftTokenId` in the response: until the lab is renamed, that is what its page URL is built from (see [Step 5](#step-5-verify-it-worked)).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect still - when the lab is minted the event is indexed the BE seeds the shortname with a default, which is lab-<token-id>

Comment thread api-reference/README.md Outdated

***

### 📊 IPNFT API (Deprecated)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd remove this section


***

## Then what

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to "Next"

| `UPSTREAM_UNAVAILABLE` | A dependency is down (`reason: KAMU`) | `retryable: true` — retry with backoff |

DID-linking for the new lab starts automatically in the background; [`getDidLinkStatus`](../labs-api/lab-management.md#get-did-link-status) reports its progress. You do not need to wait for it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put a sentence here stating that if the user is wants to upload an encrypted their file, to jump to the upload-encrypted-file tutorial with a link - otherwise they should proceed.

Comment thread api-reference/authentication.md Outdated
## Obtaining API Access

All Molecule APIs require authentication with a consumer credential. To request access:
Every request carries a **consumer credential** in the `Authorization` header. Request one on the [Molecule Discord](https://t.co/L0VEiy4Bjk): post in [the API channel](https://discord.com/channels/608198475598790656/832947534983987281) and ping **@ella**, using this template (the channel link needs you to be in the server — join with the invite first):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have an API Channel and this links to the General Chat Channel? I would maybe label this as General Channel so it doesn't confuse people and they go looking for the general channel

@nour-karoui
nour-karoui merged commit b30f12c into main Sep 7, 2026
2 checks passed
@nour-karoui
nour-karoui deleted the chore/getting-started-for-agents branch September 7, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants