Developer tools
For developers. Start with the developer manual to choose API work, App publishing, or project changes.
These tools are for people who publish Apps. You can check your App, prove that you control it, share access with your team, run private tests, ask for indexing, and get notices when something breaks.
Only the inspection call is public. All other calls need a user token or the
session cookie, and publish on App. See
Authentication.
Claim, proof, member, report, and indexing request IDs must be UUID text. This
also applies to report page cursors (before) and comparison IDs (other).
Both old UUID v4 and new UUID v7 values work. The API rejects other text before
it reaches storage. Invitation bearer tokens and notice IDs keep their own
formats.
Inspect an App
Section titled “Inspect an App”Anyone can inspect an App. You do not need to sign in. Inspection reads only public documents. It saves nothing and does not connect to the App.
curl -X POST https://api.example.com/v1/developer/inspect \ -H "Content-Type: application/json" \ -d '{ "input": "apps.example.com" }'input can be a domain or an HTTPS link, up to 8192 characters. The report has:
| Field | Description |
|---|---|
apps |
Each App found. eligibility is Eligible, Excluded or Unconfirmed for the public Index. stored shows the last saved index check, if any. |
issues |
Problems to fix. Each has a code, a stage, a severity (blocking or suggestion), a message, and a repair hint. |
stages |
Each check step: Input, Discovery, CardRead, CardSchema, Compatibility, PublisherAssociation and Eligibility. Each is Passed, Failed, Unconfirmed or NotChecked. |
An Eligible result does not mean the App is in the Index yet. See
App Index.
Prove that you control an App
Section titled “Prove that you control an App”Most tools need proof that you control the App. You can prove control in two ways:
| Scope | What it covers | Where you put the token |
|---|---|---|
PublisherHostname |
Every App whose Card is on this host name and listed in a Catalog on the same host name. | A DNS TXT record |
AppCard |
One App, by its exact Card URL. | The Server Card |
Start a claim
Section titled “Start a claim”curl -X POST https://api.example.com/v1/developer/claims \ -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "_tag": "PublisherHostname", "hostname": "apps.example.com" }'For one App, send
{ "_tag": "AppCard", "cardUrl": "https://apps.example.com/mcp/server-card" }.
The host name must be one exact public name, with no port, path or wildcard.
The answer has an id and a token like openbot-verification=.... The claim
lasts 24 hours.
Publish the token
Section titled “Publish the token”For a host name, add a DNS TXT record at _openbot-verification.<hostname>. Use
the full token as the value:
_openbot-verification.apps.example.com. TXT "openbot-verification=abc123..."For one Card, add the token to the Card:
{ "_meta": { "openbot/developer-verification": { "version": 1, "tokens": ["openbot-verification=abc123..."] } }}OpenBot reads the Card at its exact URL, with no redirects. Your robots.txt
must allow the OpenBot user bot.
Verify
Section titled “Verify”curl -X POST https://api.example.com/v1/developer/claims/<claim id>/verify \ -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"commandKey":"proof-check-1"}'The API returns 202 with an executionId. Reuse the same commandKey if a
reply is lost. Read /v1/executions/:executionId or subscribe to its events. On
completion, read GET /v1/developer/proofs/<claim id> for the proof. You
become the administrator for that scope. A failed check has a safe error
category and an errorId; its full cause stays in an encrypted error record. A
missing token or an expired claim gives a conflict result. A failed network
read gives an external result. Closing the event stream does not stop a check.
Use the execution cancel endpoint to stop it.
The SDK has Proofs.verifyClaim(id) and Proofs.verifyProof(id). Each returns
a command with a stable key and a send effect. Reuse that command to retry.
Keep the token in place. A proof is valid for 7 days, and OpenBot checks it again every day:
- If the token is gone, the proof becomes
Suspendedat once. - If the check cannot run, the proof becomes
Unconfirmed. It still works until it ends. OpenBot tries again after about one minute. - A
Suspendedproof gives no access.
To check now, call POST /v1/developer/proofs/:id/verify with a new
commandKey. It also returns an execution receipt. To give up a proof, call
POST /v1/developer/proofs/:id/relinquish. To cancel a claim, call
POST /v1/developer/claims/:id/revoke.
GET /v1/developer returns your Apps, your proofs, and your access grants.
Members and roles
Section titled “Members and roles”The administrator can invite other accounts.
| Role | What they can do |
|---|---|
administrator |
The account that made the proof. Everything, and manage members. |
maintainer |
Save reports, run tests, and ask for indexing. |
viewer |
Read Apps, reports and indexing requests. No changes. |
- Invite:
POST /v1/developer/proofs/:id/invitationswith{ "role": "maintainer" }or"viewer". The invitation lasts 7 days. - Send the invitation
idto the person. They read it withGET /v1/developer/invitations/:idand accept it withPOST /v1/developer/invitations/:id/accept. - Manage:
GET /v1/developer/proofs/:id/members,PUT /v1/developer/members/:idwith a newrole, andDELETE /v1/developer/members/:id. Members can also remove themselves.
To cancel an invitation, call POST /v1/developer/invitations/:id/revoke. When
a proof ends, its members lose access too.
Reports
Section titled “Reports”A report is a saved inspection. Use reports to see how your App changes over time.
| Task | Request |
|---|---|
| Save a new report | POST /v1/developer/reports with { "cardUrl" } |
| List reports for App | GET /v1/developer/reports?cardUrl=... (newest first, 50 per page, page with before) |
| Read one report | GET /v1/developer/reports/:id |
| Compare two reports | GET /v1/developer/reports/:id/compare?other=<id> |
| Delete a report you saved | DELETE /v1/developer/reports/:id |
| List reports you saved | GET /v1/developer/personal-reports |
A comparison lists the issues that are introduced, resolved and
persisting.
Private connection tests
Section titled “Private connection tests”A test connects to your App from OpenBot, like a user would. It is private. It does not save a user connection, and it does not add the App to the Index. A test ends after one hour.
- Create a test with your own UUID:
POST /v1/developer/testswith{ "id", "cardUrl", "remoteIndex" }. - If the endpoint needs settings, send them:
PUT /v1/developer/tests/:id/configurationwith an object of keys and values. The keys are the same as for connections. - Run it:
POST /v1/developer/tests/:id/runwith{ "commandKey": "test-run-1" }. It returns202and an execution receipt. Read the test withGET /v1/developer/tests/:idand follow the saved events withGET /v1/executions/:executionId/observe.
Keep the same command key when a reply is lost. A new key starts a new run after
the last run ends. The test keeps its last executionId. The SDK helper
DeveloperTests.run(id) makes a command whose send method reuses its key.
Closing the event stream does not stop the test. Cancel through the execution
API, or use test cleanup to stop the run and then remove its private data.
Cleanup waits for the run to release its connection. A later server start does
not change an active workflow run. Saved call results are reused after node
loss; an unfinished read can run again.
Each run has stages: configuration, negotiation, tools, resources,
resourceTemplates and prompts. Each stage is Passed, Failed,
NotAdvertised or NotChecked. The test status ends as Succeeded or
Failed.
Sign in during a test
Section titled “Sign in during a test”When the App needs an account, the test status becomes AwaitingAuthorization.
- Start the sign-in:
POST /v1/developer/tests/:id/authorizationwith{ "scopes": [], "commandKey": "test-sign-in-1" }. Addissuerif the App has more than one provider. The reply is202with an execution receipt. Follow its events, then read the test forauthorization.authorizationUrl.PreparingAuthorizationmeans that this work has not ended. The link and sealed flow become visible in one commit. Reuse the key after a lost reply. The SDK helperDeveloperTests.authorize(id, { scopes: [] })keeps this key. - Open
authorization.authorizationUrlin your browser and sign in. - The provider sends you to
/auth/developer/apps/callback. OpenBot then sends you to/?developerTest=<testId>. - Finish:
POST /v1/developer/tests/:id/authorization/completewith a newcommandKey. It returns a202execution receipt. Retry a lost reply with the same key.DeveloperTests.completeAuthorizationin the SDK keeps this key. Wait for the execution to end, then read the test and run it again.
The exchange keeps the caller that started sign-in as an access limit. It checks that caller and the caller that submits completion before using a saved result. Codes and tokens stay in sealed records. A stopped or lost request does not stop a submitted exchange. An exchange with no known result waits for a decision through the execution API; OpenBot does not send the code again on its own. A grant that cannot be installed gets platform cleanup.
When you are done, call POST /v1/developer/tests/:id/cleanup with a
commandKey. The cancel endpoint uses the same body. Both return 202 with
an execution receipt. DELETE /v1/developer/tests/:id?commandKey=... also
returns a receipt. Keep the key when you retry a request. The SDK has
DeveloperTests.cleanup, cancel, and remove helpers.
The test’s cleanupExecutionId names the service that owns expiry and cleanup.
Observe it through the execution API. A test stays Cancelling while its work
or token cleanup is still active. An exchange with an unknown result must be
resolved through its own execution before cleanup can end. OpenBot then revokes
the test sign-in where the provider allows it. It clears private data and marks
cleanup complete in one transaction. A delete command removes the test in that
transaction. Closing an observer does not stop cleanup.
Cleanup keeps every saved token in sealed work records before it removes the
local sign-in. Shared grant cleanup owns remote calls and retries. Private test
data stays until that work ends. cleanupRemote is Complete only after the
provider confirms revocation. It is LocalOnly if the provider has no way to
revoke the grant.
The same service owns the saved expiry timer. It also checks that the test still has App access once a minute. It can clean up after access is removed. A server restart does not reset tests or their authorization flows.
An OAuth test keeps the reason for a refused authorization step, such as
FlowExpired or CallbackMismatch. An unavailable connection gives
ConnectionUnavailable. A missing test record gives 404 DeveloperNotFound. A
network or storage failure gives 503 DeveloperUnavailable.
Ask for indexing
Section titled “Ask for indexing”You do not have to wait for a user to connect your App. Ask OpenBot to check it:
curl -X POST https://api.example.com/v1/developer/indexing \ -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "cardUrl": "https://apps.example.com/mcp/server-card", "commandKey": "index-check-1" }'OpenBot accepts the command after it checks your current rights and the App’s
restriction. A blocked App gives 403 DeveloperRestricted. The public check
runs in the background and saves its result.
Keep commandKey when you retry after a lost reply. The same key returns the
same request. Each request has an executionId. Read or observe it through
/v1/executions/:executionId. Cancel through the execution cancel endpoint.
Cancelling your request does not stop the shared public check.
The request’s status describes the shared check: Waiting, Verifying, then
Completed. Its check holds the result. The execution record gives the state
of your request, including cancellation and failure. List requests with
GET /v1/developer/indexing, or read one with GET /v1/developer/indexing/:id.
A completed request keeps its result across later checks and server restarts.
Notifications
Section titled “Notifications”OpenBot checks the Apps you manage about every hour. It sends you a notice when something changes.
Regular App and proof checks use the shared execution engine. The next check time and each result stay in PostgreSQL. A new server continues from that record. A proof change and its access notices commit in one transaction. An older check cannot replace a newer proof state. Replaying a saved App check does not send the same issue notice again.
| Kind | When |
|---|---|
IssueAppeared |
A new blocking issue appears. |
IssueResolved |
A blocking issue goes away. |
AccessChanged |
A proof changes state, or you lose member access. |
Read notices with GET /v1/developer/notifications. Use before to read older
pages. Mark one as read with POST /v1/developer/notifications/:id/read.
Issue notices are on for every App. To turn them off for one App, send
PUT /v1/developer/subscriptions with { "cardUrl": "...", "enabled": false }.
List your settings with GET /v1/developer/subscriptions.
See the full shapes in the API reference.
The data move keeps existing claims, proofs, members, invitations, Apps, reports, and notice settings. App and proof checks keep their saved next check times. A relinquished proof starts no new check. Old index requests keep their IDs and get a history execution. A saved result stays complete. A request without a saved result is interrupted because its original caller is missing. The platform can still finish the separate public check.