Submit an app for listing
The apps.listing.submit operation: POST /api/v1/apps/{id}/listing in the REST API and the apps_listing_submit tool in the MCP server.
/api/v1/apps/{id}/listingApplies to list one of your organization's apps for every organization on Ownrecord, or submits a changed
application. Ownrecord reviews it (pending) and approves it, asks for changes or rejects it, with a reason sent
to the contact you give. Once approved, the app is listed: any organization can install it, and its consent
screen shows a verified publisher.
The application names the publisher (legal name, country, website and a contact), links the app's privacy
policy, answers the current security questionnaire (apps.listing.get returns its questions and version) and
justifies every scope the app asks for. Your organization must have accepted the current Partner and App Terms
(partner_terms.accept). Submitting makes the app listed.
You can edit and submit again while the application waits for review or for your changes. Submitting again for an approved app keeps it listed for its approved scopes during the review, unless the publisher's legal name, country or website changed: then it is listed again only once approved.
At a glance
- REST:
POST /api/v1/apps/{id}/listing - MCP tool:
apps_listing_submit - Operation ID:
apps.listing.submit - Scope: Organization
- Permission:
agents:admin - Risk tier:
write - Dry run: Supported (send
Ownrecord-Dry-Run: true) - Idempotency:
Idempotency-Keyrequired - Modes: Test and live
- Authentication: API key with the
agents:adminscope - Since: M13
Request
Path parameters
| Field | Type | Description |
|---|---|---|
id | ocl_… ID |
Body
A JSON object (Content-Type: application/json):
| Field | Type | Required | Description |
|---|---|---|---|
publisher | object | Yes | |
publisher.legalName | string | Yes | The publisher's registered legal name, e.g. Ledgerly AI B.V. |
publisher.country | string | Yes | Where the publisher is registered (ISO 3166-1 alpha-2). |
publisher.websiteUrl | string | Yes | The publisher's website. |
publisher.contact | object | Yes | Who Ownrecord writes to about the listing and about incidents. |
publisher.contact.name | string | Yes | |
publisher.contact.email | string | Yes | |
privacyPolicyUrl | string | Yes | The app's privacy policy (it becomes the app's privacyPolicyUrl). |
questionnaire | object | Yes | |
questionnaire.version | integer (at least 1) | Yes | The questionnaire version answered: the current one (apps.listing.get). |
questionnaire.answers | array of object | Yes | |
questionnaire.answers[].questionId | string | Yes | |
questionnaire.answers[].answer | one of yes, no | No | For yes/no questions. |
questionnaire.answers[].details | string | No | A written answer, or details of a yes/no answer. |
scopeJustifications | array of object | Yes | |
scopeJustifications[].scope | one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document) | Yes | |
scopeJustifications[].justification | string | Yes | Why the app needs this scope: what it reads or does with it. |
Response
200 OK with a JSON object:
| Field | Type | Description |
|---|---|---|
id | ocl_… ID | |
object | "app_listing" | |
ownerOrgId | org_… ID | |
name | string | |
publisher | string | |
visibility | one of private, listed | |
appStatus | one of active, disabled | |
status | one of not_submitted, pending, changes_requested, approved, rejected | |
listed | boolean | |
scopes | array of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document) | |
approvedScopes | array of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document) or null | |
pendingScopes | array of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document) | |
submittedAt | date-time or null | |
decidedAt | date-time or null | |
reason | string or null | |
application | object or null | |
history | array of object | |
history[].action | one of submitted, withdrawn, approved, rejected, changes_requested, scopes_added, identity_changed, unlisted | |
history[].at | date-time | |
history[].by | string or null | |
history[].reason | string or null | |
history[].scopes | array of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document) | |
version | integer |
Examples
Apply to list an accounting app
POST /api/v1/apps/ocl_01m4ttwd3veh9apbewxheq5xha/listing
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"publisher": {
"legalName": "Ledgerly AI B.V.",
"country": "NL",
"websiteUrl": "https://ledgerly.example",
"contact": {
"name": "Mira de Vries",
"email": "partners@ledgerly.example"
}
},
"privacyPolicyUrl": "https://ledgerly.example/privacy",
"questionnaire": {
"version": 1,
"answers": [
{
"questionId": "data_location",
"details": "AWS eu-central-1 (Frankfurt), Germany"
},
{
"questionId": "encryption_in_transit",
"answer": "yes"
},
{
"questionId": "encryption_at_rest",
"answer": "yes"
},
{
"questionId": "credential_storage",
"answer": "yes"
},
{
"questionId": "staff_access",
"answer": "yes"
},
{
"questionId": "incident_response",
"answer": "yes"
},
{
"questionId": "subprocessors",
"answer": "yes",
"details": "AWS (hosting), Postmark (email)"
},
{
"questionId": "model_training",
"answer": "no"
},
{
"questionId": "data_deletion",
"answer": "yes",
"details": "Within 30 days of uninstalling"
},
{
"questionId": "vulnerability_management",
"answer": "yes"
}
]
},
"scopeJustifications": [
{
"scope": "filings:read",
"justification": "Reads filing periods to prepare each return."
},
{
"scope": "filings:write",
"justification": "Records the prepared return and its submission."
},
{
"scope": "tasks:read",
"justification": "Finds the filing tasks assigned to the app."
},
{
"scope": "tasks:write",
"justification": "Claims and completes filing tasks."
},
{
"scope": "ledger:read",
"justification": "Reconciles the return with the tax payable accounts."
}
]
}
Response (200):
{
"id": "ocl_01m4ttwd3veh9apbewxheq5xha",
"object": "app_listing",
"ownerOrgId": "org_01m4ttwd3veh9apbewxheq5xhb",
"name": "Ledgerly AI",
"publisher": "Ledgerly AI B.V.",
"visibility": "listed",
"appStatus": "active",
"status": "pending",
"listed": false,
"scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write", "ledger:read"],
"approvedScopes": null,
"pendingScopes": ["filings:read", "filings:write", "tasks:read", "tasks:write", "ledger:read"],
"submittedAt": "2026-10-30T09:00:00.000Z",
"decidedAt": null,
"reason": null,
"application": {
"publisher": {
"legalName": "Ledgerly AI B.V.",
"country": "NL",
"websiteUrl": "https://ledgerly.example",
"contact": {
"name": "Mira de Vries",
"email": "partners@ledgerly.example"
}
},
"privacyPolicyUrl": "https://ledgerly.example/privacy",
"questionnaire": {
"version": 1,
"answers": [
{
"questionId": "data_location",
"answer": null,
"details": "AWS eu-central-1 (Frankfurt), Germany"
},
{
"questionId": "encryption_in_transit",
"answer": "yes",
"details": null
},
{
"questionId": "encryption_at_rest",
"answer": "yes",
"details": null
},
{
"questionId": "credential_storage",
"answer": "yes",
"details": null
},
{
"questionId": "staff_access",
"answer": "yes",
"details": null
},
{
"questionId": "incident_response",
"answer": "yes",
"details": null
},
{
"questionId": "subprocessors",
"answer": "yes",
"details": "AWS (hosting), Postmark (email)"
},
{
"questionId": "model_training",
"answer": "no",
"details": null
},
{
"questionId": "data_deletion",
"answer": "yes",
"details": "Within 30 days of uninstalling"
},
{
"questionId": "vulnerability_management",
"answer": "yes",
"details": null
}
]
},
"scopeJustifications": [
{
"scope": "filings:read",
"justification": "Reads filing periods to prepare each return."
},
{
"scope": "filings:write",
"justification": "Records the prepared return and its submission."
},
{
"scope": "tasks:read",
"justification": "Finds the filing tasks assigned to the app."
},
{
"scope": "tasks:write",
"justification": "Claims and completes filing tasks."
},
{
"scope": "ledger:read",
"justification": "Reconciles the return with the tax payable accounts."
}
],
"partnerTermsVersion": "1.0",
"submittedBy": "usr_01m4ttwd3veh9apbewxheq5xhf"
},
"history": [
{
"action": "submitted",
"at": "2026-10-30T09:00:00.000Z",
"by": "usr_01m4ttwd3veh9apbewxheq5xhf",
"reason": null,
"scopes": []
}
],
"version": 3
}
Errors
| Code | HTTP status | When | What to do |
|---|---|---|---|
app_listing_incomplete | 400 | A required question isn't answered, a scope isn't justified, or the questionnaire version isn't the current one. | Answer every required question of the current security questionnaire, justify every scope the app asks for, and give a privacy policy URL. details.problems lists what's missing. |
partner_terms_not_accepted | 409 | Your organization hasn't accepted the current Partner and App Terms. | An owner or admin reads the Partner and App Terms at /legal/partners and accepts them under Developers → Apps (or with partner_terms.accept), then submits the application again. |
oauth_client_disabled | 409 | The app is disabled. | Register a new app, or create a new agent. |
validation_failed | 400 | A field is invalid, e.g. a URL that isn't https. | Fix the fields listed in details.issues and send the request again. |
resource_not_found | 404 | Your organization registered no app with this ID. | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode. |
version_conflict | 409 | The app changed at the same moment; retry. | Fetch the object again and retry the change. |
permission_denied | 403 | You can't manage the organization's apps. | Ask an organization owner or admin for a role or scope that allows this action. |
It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, idempotency_key_required, idempotency_key_reused, idempotency_in_progress, approval_required, approval_rejected, approval_expired, stale_approval, approval_policy_denied, rate_limited, internal_error.