Brand Stores¶
Introduction¶
The Brand Stores API provides a means to manipulate brand stores.
More information on brand stores and how to setup one can be obtained here.
This documentation assumes the brand store already exists, and the reader
knows its store ID, e.g. via Accounts response. We’ll call this
store ID the-store-id
in the examples.
All endpoints require a macaroon authenticated request, for a user that has
admin permission in the store identified by the given store ID. Also, the
macaroon used to authenticate the request must contain the store_admin
permission.
See Macaroons for details on how to obtain a suitable macaroon for
interacting with this API. You could also use the surl
command line,
available as a snap:
snap install surl
to exercise this (and other) store APIs. The following example authenticates to the store and saves the macaroon locally for use in subsequent requests:
surl -a prod -s production -e <email> [[-p <macaroon_permission>]]
surl -a prod -X <http-method> https://dashboard.snapcraft.io/<endpoint> [[-d '<json payload>']]
If you call surl with the --allowed-store some-store-id
the saved
macaroon will be attenuated and next calls using that macaroon will be
able to operate on that store only (or stores, if you pass the option
several times).
See surl help
for the details about these and other surl options.
The Brand Stores API is exposed at the following base URLs:
Staging: https://dashboard.staging.snapcraft.io/api/v2/stores
Production: https://dashboard.snapcraft.io/api/v2/stores
Response Format¶
JSON will be returned in all responses from the API, including error responses, please refer to the following section for details about the format.
List the details of a brand store¶
Introduced in version 1
- GET /api/v2/stores/(?P<store_id>[\\w_-]+)¶
List the details of a brand store.
- reqheader Authorization
macaroon authorization header for a store admin
- status 200
success
- status 400
error
- status 401
authentication required
- status 404
store id does not exist or user does not have enough permission to see it
Usage¶
As mentioned in the introduction, this endpoint can be exercised using the
surl
command as follows:
surl -p store_admin -s production -e your-email@example.com -X GET
https://dashboard.snapcraft.io/api/v2/stores/the-store-id
where your-email@example.com
has to be an existing account with
permission to manage the store with ID the-store-id
.
If the authenticating macaroon has any store_ids
attenuation, the store
the-store-id
has to be among the allowed stores in that constraint
(see the --allowed-store
option in surl as a way to get a macaroon
with such attenuation).
Example¶
Retrieve the details for the brand store with id the-store-id
.
Request:
GET /api/v2/stores/the-store-id HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"parent": "store-parent-id",
"private": true,
"manual-review-policy": "allow",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
}
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
},
"users": [
{
"displayname": "Test User 0",
"email": "test-user-0@example.com",
"id": "AccountID32LenForXtestuser0XXXXX",
"roles": [
"admin"
],
"username": "test-user-0"
},
{
"displayname": "Test User 1",
"email": "test-user-1@example.com",
"id": "AccountID32LenForXtestuser1XXXXX",
"roles": [
"review"
],
"username": "test-user-1"
}
]
}
Errors¶
If the given store ID does not exist, or the authenticated user does not have enough permissions to manage such store, the following error response is returned:
Request:
GET /api/v2/stores/other-store-id HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "resource-not-found",
"message": "The resource requested does not exist or credentials are not sufficient to access it."
}
]
}
If the given authenticating macaroon (from the Authorization
header) does
not have the proper store_admin
permission (caveat), the following error
response is returned:
Request:
GET /api/v2/stores/the-store-id HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "macaroon-permission-required",
"extra": {
"permission": "store_admin"
},
"message": "Missing permission required as a macaroon caveat."
}
]
}
If the given authenticating macaroon (from the Authorization
header) is
attenuated to work on some specific stores (store_ids
being
for example ["store1", "store2"]
, and the call is on an store not
included in that list (say, store3
), the following error response
is returned:
Request:
GET /api/v2/stores/store3 HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "macaroon-permission-required",
"extra": {
"given": "store3",
"allowed": ["store1", "store2"],
"permission": "store_admin"
},
"message": "Store-restricted authorization does not allow this operation."
}
]
}
Changelog¶
Version 31: Added extra field to store info dict: “brand-id”.
Version 28: Added extra field to store info dict: parent.
Version 17: Added extra fields to store info dict: private, manual-review-policy
Version 5: Added extra field to store info dict: invites.
Version 3: Added extra field to store info dict: snap-name-prefixes.
Version 2: Added extra fields to store info dict: allowed-inclusion-source-stores and allowed-inclusion-target-stores.
Response JSON Schema¶
{
"additionalProperties": false,
"properties": {
"invites": {
"introduced_at": 5,
"items": {
"additionalProperties": false,
"properties": {
"email": {
"description": "The invited email",
"type": "string"
},
"expiration-date": {
"description": "The date when this invite expires, in ISO 8601 format.",
"format": "date-time",
"type": "string"
},
"roles": {
"description": "The roles that this invite's email was invited to join with.",
"items": {
"type": "string"
},
"type": "array"
},
"status": {
"description": "The status of the invite, can be one of Pending, Accepted, Revoked or Expired.",
"enum": [
"Pending",
"Revoked",
"Expired"
],
"type": "string"
}
},
"required": [
"email",
"expiration-date",
"roles",
"status"
],
"type": "object"
},
"type": "array"
},
"store": {
"additionalProperties": false,
"properties": {
"allowed-inclusion-source-stores": {
"description": "The list of store IDs that this store can select snaps from to include in this store.",
"introduced_at": 2,
"items": {
"type": "string"
},
"type": "array"
},
"allowed-inclusion-target-stores": {
"description": "The list of store IDs that can select snaps from this store to include in their stores.",
"introduced_at": 2,
"items": {
"type": "string"
},
"type": "array"
},
"brand-id": {
"description": "The store brand ID or null.",
"type": [
"string",
"null"
]
},
"id": {
"description": "The store ID (slug).",
"type": "string"
},
"manual-review-policy": {
"description": "The review policy for the store (one of: \"allow\", \"avoid\", or \"require\").",
"enum": [
"allow",
"avoid",
"require"
],
"introduced_at": 17,
"type": "string"
},
"name": {
"description": "Visible name of this store.",
"type": "string"
},
"parent": {
"description": "The store parent's ID (slug).",
"type": [
"null",
"string"
]
},
"private": {
"description": "Indicate whether this store will be visible in public lists",
"introduced_at": 17,
"type": "boolean"
},
"roles": {
"description": "The list of available roles in this store.",
"items": {
"type": "string"
},
"type": "array"
},
"snap-name-prefixes": {
"additionalProperties": false,
"introduced_at": 3,
"properties": {
"inheritable": {
"description": "Whether this snap name prefix can be used by child stores or not (default is False).",
"type": "boolean"
},
"parent-id": {
"description": "None if this snap name prefix was defined for the requesting store, else the store ID from which this prefix is being inherited.",
"type": [
"string",
"null"
]
},
"prefix": {
"description": "The snap name prefix.",
"type": "string"
}
},
"required": [
"prefix",
"inheritable",
"parent-id"
],
"type": "object"
},
"store-whitelist": {
"description": "The list of store IDs that this store includes.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id",
"brand-id",
"name",
"roles",
"store-whitelist",
"snap-name-prefixes",
"allowed-inclusion-source-stores",
"allowed-inclusion-target-stores"
],
"type": "object"
},
"users": {
"introduced_at": 1,
"items": {
"additionalProperties": false,
"properties": {
"displayname": {
"description": "The full name",
"type": "string"
},
"email": {
"description": "The email",
"type": "string"
},
"id": {
"description": "The account ID",
"type": "string"
},
"roles": {
"description": "The roles that this user has in this store",
"items": {
"type": "string"
},
"type": "array"
},
"username": {
"description": "The store username",
"type": "string"
}
},
"required": [
"id",
"displayname ",
"email",
"username",
"roles"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"store",
"users",
"invites"
],
"type": "object"
}
Manage the snaps in a brand store¶
Introduced in version 1
- GET /api/v2/stores/(?P<store_id>[\\w_-]+)/snaps¶
- POST /api/v2/stores/(?P<store_id>[\\w_-]+)/snaps¶
Managing the snaps in a brand store.
In order to manage the snaps in a brand store, a store admin can GET or
POST to the endpoint /api/v2/stores/(the-store-id)/snaps
, to get a list
of the snaps appearing in their store (GET operation), or to add/remove
individual snaps.
GET¶
For listing the snaps appearing in the store with ID the-store-id
:
- reqheader Authorization
macaroon authorization header for a store admin
- status 200
success
- status 400
error
- status 401
authentication required
- status 404
store id does not exist or user does not have enough permission to see it
Please note that the returned list of snaps include:
The snaps registered and uploaded to this store.
The snaps added into this store by using this API.
Snaps included in this store by means of store inclusion (inheritance) are not returned in the result.
There are two optional filters that could be passed when doing the GET call as follows:
allowed-for-inclusion=1
publisher=(an-account-id)
POST¶
For adding a snap to the store with ID the-store-id
:
- POST /api/v2/stores/(the-store-id)/snaps¶
- reqheader Authorization
macaroon authorization header for a store admin
- status 200
success
- status 400
error
- status 401
authentication required
- status 404
store id does not exist or user does not have enough permission to see it
{"add": [{"name": "snap-name-1"}, {"name": "snap-name-2"}]}
The snaps that can be selected for addition are the ones that:
are public snaps in the main store, or
are public snaps in other stores that have explicitely allowed this store to add snaps from.
For removing a snap from the store with ID the-store-id
:
- POST /api/v2/stores/(the-store-id)/snaps¶
- reqheader Authorization
macaroon authorization header for a store admin
- status 200
success
- status 400
error
- status 401
authentication required
- status 404
store id does not exist or user does not have enough permission to see it
{"remove": [{"name": "snap-name-1"}, {"name": "snap-name-2"}]}
The snaps that can be removed are only those that were previously added by using this API.
The operations of add
and remove
can be combined in a single
request, to reduce the amount of requests needed for each operation (see
the the examples section).
Usage¶
As mentioned in the introduction and in the previous endpoints, this
endpoint can be exercised using the surl
command.
If the authenticating macaroon has any store_ids
attenuation, the store
the-store-id
has to be among the allowed stores in that constraint
(see the --allowed-store
option in surl as a way to get a macaroon
with such attenuation).
Example¶
List the snaps for the store with ID the-store-id
.
Request:
GET /api/v2/stores/the-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"snaps": [
{
"essential": true,
"id": "SnapID32LenForXcoreXXXXXXXXXXXXX",
"name": "core",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
},
{
"displayname": "The Collaborator",
"roles": [
"collaborator"
],
"username": "bar"
}
],
"store": "ubuntu"
},
{
"essential": false,
"id": "SnapID32LenForXexample0XXXXXXXXX",
"name": "example-0",
"other-stores": [
"lorem-public"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXexample1XXXXXXXXX",
"name": "example-1",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXexample2XXXXXXXXX",
"name": "example-2",
"other-stores": [
"ipsum-public",
"lorem-public"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
}
],
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"parent": "store-parent-id",
"private": true,
"manual-review-policy": "allow",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
}
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
}
}
Search for snaps in the store with ID the-store-id
.
Request:
GET /api/v2/stores/the-store-id/snaps?q=core HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"snaps": [
{
"essential": true,
"id": "SnapID32LenForXcoreXXXXXXXXXXXXX",
"name": "core",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
},
{
"displayname": "The Collaborator",
"roles": [
"collaborator"
],
"username": "bar"
}
],
"store": "ubuntu"
}
],
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"parent": "store-parent-id",
"private": true,
"manual-review-policy": "allow",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
}
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
}
}
Search for snaps (in other allowed stores) to include in store with ID the-store-id
.
Request:
GET /api/v2/stores/the-store-id/snaps?q=example&allowed-for-inclusion=1 HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"snaps": [
{
"essential": false,
"id": "SnapID32LenForXexample0XXXXXXXXX",
"name": "example-0",
"other-stores": [
"lorem-public"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "other-store-id"
}
],
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"parent": "store-parent-id",
"private": true,
"manual-review-policy": "allow",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
}
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
}
}
Add the snap “network-manager” from the main store into the store with
ID the-store-id
.
Request:
POST /api/v2/stores/the-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
{"add": [{"name": "network-manager"}]}
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"snaps": [
{
"essential": true,
"id": "SnapID32LenForXcoreXXXXXXXXXXXXX",
"name": "core",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
},
{
"essential": false,
"id": "SnapID32LenForXexample0XXXXXXXXX",
"name": "example-0",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXexample1XXXXXXXXX",
"name": "example-1",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXexample2XXXXXXXXX",
"name": "example-2",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXnetworkmanagerXXX",
"name": "network-manager",
"other-stores": [
"the-store-id"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
}
],
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"parent": "store-parent-id",
"private": true,
"manual-review-policy": "allow",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
}
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
}
}
Adding snaps can be done in bulk, this means, a store admin can choose to add many snaps at the same time.
Request:
POST /api/v2/stores/the-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
{"add": [{"name": "bluez"}, {"name": "modem-manager"}]}
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"snaps": [
{
"essential": false,
"id": "SnapID32LenForXbluezXXXXXXXXXXXX",
"name": "bluez",
"other-stores": [
"the-store-id"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
},
{
"essential": true,
"id": "SnapID32LenForXcoreXXXXXXXXXXXXX",
"name": "core",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
},
{
"essential": false,
"id": "SnapID32LenForXexample0XXXXXXXXX",
"name": "example-0",
"other-stores": [
"lorem-public"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXexample1XXXXXXXXX",
"name": "example-1",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXexample2XXXXXXXXX",
"name": "example-2",
"other-stores": [
"ipsum-public",
"lorem-public"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXmodemmanagerXXXXX",
"name": "modem-manager",
"other-stores": [
"the-store-id"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
},
{
"essential": false,
"id": "SnapID32LenForXnetworkmanagerXXX",
"name": "network-manager",
"other-stores": [
"the-store-id"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
}
],
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"parent": "store-parent-id",
"private": true,
"manual-review-policy": "allow",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
}
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
}
}
Snaps added via this API can also be removed issuing a POST request.
Request:
POST /api/v2/stores/the-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
{"remove": [{"name": "bluez"}]}
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"snaps": [
{
"essential": true,
"id": "SnapID32LenForXcoreXXXXXXXXXXXXX",
"name": "core",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
},
{
"essential": false,
"id": "SnapID32LenForXexample0XXXXXXXXX",
"name": "example-0",
"other-stores": [
"lorem-public"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXexample1XXXXXXXXX",
"name": "example-1",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXexample2XXXXXXXXX",
"name": "example-2",
"other-stores": [
"ipsum-public",
"lorem-public"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXmodemmanagerXXXXX",
"name": "modem-manager",
"other-stores": [
"the-store-id"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
},
{
"essential": false,
"id": "SnapID32LenForXnetworkmanagerXXX",
"name": "network-manager",
"other-stores": [
"the-store-id"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
}
],
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"parent": "store-parent-id",
"private": true,
"manual-review-policy": "allow",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
}
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
}
}
Lastly, adding and removing snap can be combined in the same POST request.
Request:
POST /api/v2/stores/the-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
{"add": [{"name": "bluez"}, {"name": "wifi-ap"}], "remove": [{"name": "modem-manager"}]}
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"snaps": [
{
"essential": false,
"id": "SnapID32LenForXbluezXXXXXXXXXXXX",
"name": "bluez",
"other-stores": [
"the-store-id"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
},
{
"essential": true,
"id": "SnapID32LenForXcoreXXXXXXXXXXXXX",
"name": "core",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
},
{
"essential": false,
"id": "SnapID32LenForXexample0XXXXXXXXX",
"name": "example-0",
"other-stores": [
"lorem-public"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXexample1XXXXXXXXX",
"name": "example-1",
"other-stores": [],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXexample2XXXXXXXXX",
"name": "example-2",
"other-stores": [
"ipsum-public",
"lorem-public"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "the-store-id"
},
{
"essential": false,
"id": "SnapID32LenForXnetworkmanagerXXX",
"name": "network-manager",
"other-stores": [
"the-store-id"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
},
{
"essential": false,
"id": "SnapID32LenForXwifiapXXXXXXXXXXX",
"name": "wifi-ap",
"other-stores": [
"the-store-id"
],
"private": false,
"latest-release": {
"revision": 1,
"channel": "stable",
"timestamp": "2021-01-01T00:00:00.00000+00:00",
"version": "1"
},
"users": [
{
"displayname": "The Publisher",
"roles": [
"owner"
],
"username": "foo"
}
],
"store": "ubuntu"
}
],
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
}
}
Errors¶
If the given store ID does not exist, or the authenticated user does not have enough permissions to manage such store, the following error response is returned:
Request:
GET /api/v2/stores/other-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 404 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "resource-not-found",
"message": "The resource requested does not exist or credentials are not sufficient to access it."
}
]
}
If the given authenticating macaroon (from the Authorization
header) does
not have the proper store_admin
permission (caveat), the following error
response is returned:
Request:
GET /api/v2/stores/the-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 403 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "macaroon-permission-required",
"extra": {
"permission": "store_admin"
},
"message": "Missing permission required as a macaroon caveat."
}
]
}
When POSTing, if the POST body is not JSON or is not a dictionary with two keys “add” and “remove”, each one being a list of dictionaries with key “name” and the desired snap name as value, the following error response is returned:
Request:
POST /api/v2/stores/the-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
"foobar"
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "bad-request",
"extra": {
"data": "foobar"
},
"message": "Data should be a dictionary with two keys: \"add\" and \"remove\". Each key should map to a list of dicts (with field \"name\" for each snap name)"
}
]
}
If the list of snap names requested to be either added or removed, contains invalid snap names (these are either non-existent or private snaps; or snaps that were already added in the store when requested a re-add or snaps that were never added to the store when requested to be removed), the following error response is returned:
Request:
POST /api/v2/stores/the-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
{"add": [{"name": "foobar"}], "remove": [{"name": "modem-manager"}]}
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "bad-request",
"extra": {
"invalid": [
"foobar"
]
},
"message": "The given snap list for \"add\" contains snaps that do not exist or are not available."
},
{
"code": "bad-request",
"extra": {
"invalid": [
"modem-manager"
]
},
"message": "The given snap list for \"remove\" contains snaps that do not exist or are not available."
}
]
}
If the list of snap names requested to be added or removed contains duplicated snap names, the following error response is returned:
Request:
POST /api/v2/stores/the-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
{"add": [{"name": "bluez"}, {"name": "bluez"}]}
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "bad-request",
"extra": {
"duplicates": [
"bluez",
"bluez"
]
},
"message": "The given snap list for \"add\" contains duplicates."
}
]
}
If a snap was not added to the store via this API (ie, the snap was registered directly to the store), it can not be removed. If such action is requested, the following error response is returned:
Request:
POST /api/v2/stores/the-store-id/snaps HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
{"remove": [{"name": "example-0"}]}
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "bad-request",
"extra": {
"invalid": [
"example-0"
]
},
"message": "The given snap list for \"remove\" contains snaps that do not exist or are not available."
}
]
}
Changelog¶
Version 31: Added extra field to store info dict: “brand-id”.
Version 28: Added extra field to store info dict: parent.
Version 25: Add searching of snaps for inclusion in brand store using query params: q and allowed-for-inclusion
Version 24: Remove fields from store snaps endpoint: user id, user email
Version 23: Add searching of snaps to store snaps endpoint using query param: q
Version 22: Added extra field to store snaps GET response: latest-release
Version 21: Added extra field to store snaps response: users (publisher and collaborators).
Version 17: Added extra fields to store info dict: private, manual-review-policy
Version 3: Added extra field to store info dict: snap-name-prefixes.
Version 2: Added extra fields to store info dict: allowed-inclusion-source-stores and allowed-inclusion-target-stores.
Request JSON Schema¶
{
"additionalProperties": false,
"properties": {
"add": {
"description": "list of snap names to add to store",
"introduced_at": 1,
"items": {
"type": "string"
},
"type": "array"
},
"remove": {
"description": "list of snap names to remove from store",
"introduced_at": 1,
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"add",
"remove"
],
"type": "object"
}
Response JSON Schema¶
{
"additionalProperties": false,
"properties": {
"snaps": {
"items": {
"additionalProperties": false,
"properties": {
"essential": {
"description": "Whether this snap is essential or not",
"type": "boolean"
},
"id": {
"description": "The snap ID",
"type": "string"
},
"latest-release": {
"description": "Details of the latest release. Only populated in GET requests.",
"introduced_at": 22,
"properties": {
"channel": {
"description": "Channel of latest release",
"type": [
"string",
"null"
]
},
"revision": {
"description": "Revision of latest release",
"type": [
"integer",
"null"
]
},
"timestamp": {
"description": "Timestamp of latest release",
"type": [
"string",
"null"
]
},
"version": {
"description": "Version of latest release",
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"name": {
"description": "The snap name",
"type": "string"
},
"other-stores": {
"description": "The list of store IDs that this snap appears in",
"items": {
"type": "string"
},
"type": "array"
},
"private": {
"description": "Whether this snap is private or not",
"type": "boolean"
},
"store": {
"description": "The store this snap was originally registered to",
"type": "string"
},
"users": {
"description": "Publisher and collaborators for this snap",
"introduced_at": 21,
"items": {
"additionalProperties": false,
"properties": {
"displayname": {
"description": "The full name",
"type": "string"
},
"roles": {
"description": "The roles that this user has for this snap",
"items": {
"enum": [
"owner",
"collaborator"
],
"type": "string"
},
"type": "array"
},
"username": {
"description": "The store username",
"type": "string"
}
},
"required": [
"displayname ",
"username",
"roles"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"id",
"essential",
"name",
"private",
"store",
"users"
],
"type": "object"
},
"type": "array"
},
"store": {
"additionalProperties": false,
"properties": {
"allowed-inclusion-source-stores": {
"description": "The list of store IDs that this store can select snaps from to include in this store.",
"introduced_at": 2,
"items": {
"type": "string"
},
"type": "array"
},
"allowed-inclusion-target-stores": {
"description": "The list of store IDs that can select snaps from this store to include in their stores.",
"introduced_at": 2,
"items": {
"type": "string"
},
"type": "array"
},
"brand-id": {
"description": "The store brand ID or null.",
"type": [
"string",
"null"
]
},
"id": {
"description": "The store ID (slug).",
"type": "string"
},
"manual-review-policy": {
"description": "The review policy for the store (one of: \"allow\", \"avoid\", or \"require\").",
"enum": [
"allow",
"avoid",
"require"
],
"introduced_at": 17,
"type": "string"
},
"name": {
"description": "Visible name of this store.",
"type": "string"
},
"parent": {
"description": "The store parent's ID (slug).",
"type": [
"null",
"string"
]
},
"private": {
"description": "Indicate whether this store will be visible in public lists",
"introduced_at": 17,
"type": "boolean"
},
"roles": {
"description": "The list of available roles in this store.",
"items": {
"type": "string"
},
"type": "array"
},
"snap-name-prefixes": {
"additionalProperties": false,
"introduced_at": 3,
"properties": {
"inheritable": {
"description": "Whether this snap name prefix can be used by child stores or not (default is False).",
"type": "boolean"
},
"parent-id": {
"description": "None if this snap name prefix was defined for the requesting store, else the store ID from which this prefix is being inherited.",
"type": [
"string",
"null"
]
},
"prefix": {
"description": "The snap name prefix.",
"type": "string"
}
},
"required": [
"prefix",
"inheritable",
"parent-id"
],
"type": "object"
},
"store-whitelist": {
"description": "The list of store IDs that this store includes.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id",
"brand-id",
"name",
"roles",
"store-whitelist",
"snap-name-prefixes",
"allowed-inclusion-source-stores",
"allowed-inclusion-target-stores"
],
"type": "object"
}
},
"required": [
"store",
"snaps"
],
"type": "object"
}
Add, remove or edit users’ roles¶
Introduced in version 1
- GET /api/v2/stores/(?P<store_id>[\\w_-]+)/users¶
- POST /api/v2/stores/(?P<store_id>[\\w_-]+)/users¶
Add, remove or edit users’ roles.
- reqheader Authorization
macaroon authorization header for a store admin
- status 200
success
- status 400
error
- status 401
authentication required
- status 404
store id does not exist or user does not have enough permission to see it
Usage¶
As mentioned in the introduction, this endpoint can be exercised using the
surl
command as follows:
GET:
surl -p store_admin -s production -e your-email@example.com https://dashboard.snapcraft.io/api/v2/stores/the-store-id/users
POST:
surl -p store_admin -s production -e your-email@example.com -X POST -d '[{"email": "some-email@example.com", "roles": ["view", "access"]}]' https://dashboard.snapcraft.io/api/v2/stores/the-store-id/users
where your-email@example.com
has to be an existing account with
permission to manage the store with ID the-store-id
.
If the authenticating macaroon has any store_ids
attenuation, the store
the-store-id
has to be among the allowed stores in that constraint
(see the --allowed-store
option in surl as a way to get a macaroon
with such attenuation).
Examples¶
Get users in store with ID the-store-id
.
Request:
GET /api/v2/stores/the-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"parent": "store-parent-id",
"private": true,
"manual-review-policy": "allow",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
}
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
},
"users": [
{
"displayname": "Bar",
"email": "bar@example.com",
"id": "12345678901234567890123456789012",
"roles": [
"view"
],
"username": "bar"
},
{
"displayname": "Foo",
"email": "foo@example.com",
"id": "AccountID32LenForXfooXXXXXXXXXXX",
"roles": [
"review"
],
"username": "foo"
},
{
"displayname": "Test User 0",
"email": "test-user-0@example.com",
"id": "AccountID32LenForXtestuser0XXXXX",
"roles": [
"admin"
],
"username": "test-user-0"
},
{
"displayname": "Test User 1",
"email": "test-user-1@example.com",
"id": "AccountID32LenForXtestuser1XXXXX",
"roles": [
"review"
],
"username": "test-user-1"
}
]
}
Add the store account for email foo@example.com
as a reviewer, and the
store account with account ID 12345678901234567890123456789012
as viewer,
to store with ID the-store-id
.
Request:
POST /api/v2/stores/the-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
[
{"email": "foo@example.com", "roles": ["review"]},
{"id": "12345678901234567890123456789012", "roles": ["view"]}
]
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"parent": "store-parent-id",
"private": true,
"manual-review-policy": "allow",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
}
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
},
"users": [
{
"displayname": "Bar",
"email": "bar@example.com",
"id": "12345678901234567890123456789012",
"roles": [
"view"
],
"username": "bar"
},
{
"displayname": "Foo",
"email": "foo@example.com",
"id": "AccountID32LenForXfooXXXXXXXXXXX",
"roles": [
"review"
],
"username": "foo"
},
{
"displayname": "Test User 0",
"email": "test-user-0@example.com",
"id": "AccountID32LenForXtestuser0XXXXX",
"roles": [
"admin"
],
"username": "test-user-0"
},
{
"displayname": "Test User 1",
"email": "test-user-1@example.com",
"id": "AccountID32LenForXtestuser1XXXXX",
"roles": [
"review"
],
"username": "test-user-1"
}
]
}
Add role admin
to the store account for foo@example.com
. Email
addresses are matched in a case-insensitive way (i.e. Foo@Example.com
will
also work in this example). Please note that roles should be the full list of
desired permissions, POST requests to this endpoint are not additive:
Request:
POST /api/v2/stores/the-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
[{"email": "foo@example.com", "roles": ["review", "admin"]}]
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"store": {
"allowed-inclusion-source-stores": [],
"allowed-inclusion-target-stores": [],
"id": "the-store-id",
"brand-id": "the-brand-id",
"name": "The Example",
"parent": "store-parent-id",
"private": true,
"manual-review-policy": "allow",
"roles": [
{
"description": "Admins manage the store's users and roles, and control the store's settings.",
"label": "Admin",
"role": "admin"
},
{
"description": "Reviewers can approve or reject snaps, and edit snap declarations.",
"label": "Reviewer",
"role": "review"
},
{
"description": "Viewers are read-only roles and can view snap details, metrics, and the contents of this store.",
"label": "Viewer",
"role": "view"
},
{
"description": "Publishers can invite collaborators to a snap, publish snaps and update snap details.",
"label": "Publisher",
"role": "access"
}
],
"snap-name-prefixes": [
{
"inheritable": false,
"parent-id": null,
"prefix": "the-example"
}
],
"store-whitelist": []
},
"users": [
{
"displayname": "Bar",
"email": "bar@example.com",
"id": "12345678901234567890123456789012",
"roles": [
"view"
],
"username": "bar"
},
{
"displayname": "Foo",
"email": "foo@example.com",
"id": "AccountID32LenForXfooXXXXXXXXXXX",
"roles": [
"admin",
"review"
],
"username": "foo"
},
{
"displayname": "Test User 0",
"email": "test-user-0@example.com",
"id": "AccountID32LenForXtestuser0XXXXX",
"roles": [
"admin"
],
"username": "test-user-0"
},
{
"displayname": "Test User 1",
"email": "test-user-1@example.com",
"id": "AccountID32LenForXtestuser1XXXXX",
"roles": [
"review"
],
"username": "test-user-1"
}
]
}
Errors¶
If the request does not send proper email or id fields, an error response is returned as shown below:
Request:
POST /api/v2/stores/the-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
[{"username": "foobarbaz", "roles": ["review"]}]
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "missing-field",
"extra": {
"expected": [
"email",
"id",
"roles"
],
"given": {
"roles": [
"review"
],
"username": "foobarbaz"
}
},
"message": "Required fields are missing."
}
]
}
If the store has no record of an account for the given email address, an error response is returned as shown below:
Request:
POST /api/v2/stores/the-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
[{"email": "does-not-exist@example.com", "roles": ["review"]}]
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "store-users-no-match",
"extra": {
"email": "does-not-exist@example.com",
"roles": [
"review"
]
},
"message": "There is no user defined for the given user information."
}
]
}
If the store has no record of an account for the given account ID, an error response is returned as shown below:
Request:
POST /api/v2/stores/the-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
[{"id": "does-not-exist", "roles": ["review"]}]
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "store-users-no-match",
"extra": {
"id": "does-not-exist",
"roles": [
"review"
]
},
"message": "There is no user defined for the given user information."
}
]
}
If the store has more than one account for the given email address, an error response is returned requesting that the account ID is also sent to be able to identify the user:
Request:
POST /api/v2/stores/the-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
[{"email": "duplicated@example.com", "roles": ["review"]}]
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "store-users-multiple-matches",
"extra": {
"email": "duplicated@example.com",
"roles": [
"review"
]
},
"message": "There is more than one user for the given email, please retry sending the account ID to disambiguate."
}
]
}
If the request to edit users roles has no effect (i.e. there are no changes to apply), an error response is returned as shown below:
Request:
POST /api/v2/stores/the-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
[
{"email": "foo@example.com", "roles": ["admin", "review"]},
{"id": "12345678901234567890123456789012", "roles": ["view"]}
]
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "store-users-no-role-change",
"extra": {
"email": "foo@example.com",
"roles": [
"admin",
"review"
]
},
"message": "No role change requested for the given user information."
},
{
"code": "store-users-no-role-change",
"extra": {
"id": "12345678901234567890123456789012",
"roles": [
"view"
]
},
"message": "No role change requested for the given user information."
}
]
}
A store admin can not demote him/herself. If the request includes changes for the authenticated user which would leave the user without admin access, an error response is returned as follows:
Request:
POST /api/v2/stores/the-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
[{"email": "your-email@example.com", "roles": ["review"]}]
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "store-users-same-user",
"extra": {
"email": "your-email@example.com",
"roles": [
"review"
]
},
"message": "You can not demote yourself by removing your admin role."
}
]
}
If the request includes an invalid role, an error response is returned as follows:
Request:
POST /api/v2/stores/the-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
[{"email": "foo@example.com", "roles": ["review", "foo"]}]
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "invalid-choice",
"extra": {
"field": "roles",
"value": "foo"
},
"message": "Select a valid choice. The given value is not one of the available choices."
}
]
}
If the given store ID does not exist, or the authenticated user does not have permissions to manage such store, the following error response is returned:
Request:
GET /api/v2/stores/other-store-id/users HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 404 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "resource-not-found",
"message": "The resource requested does not exist or credentials are not sufficient to access it."
}
]
}
Changelog¶
Version 31: Added extra field to store info dict: “brand-id”.
Version 28: Added extra field to store info dict: parent.
Version 20: Add GET support to store users endpoint.
Version 17: Added extra fields to store info dict: private, manual-review-policy
Version 3: Added extra field to store info dict: snap-name-prefixes.
Version 2: Added extra fields to store info dict: allowed-inclusion-source-stores and allowed-inclusion-target-stores.
Request JSON Schema¶
{
"introduced_at": 1,
"items": {
"properties": {
"email": {
"description": "The primary email for the Ubuntu One account.",
"format": "email",
"title": "Email address",
"type": "string"
},
"id": {
"description": "Used to disambiguate if there are multiple users for the given email.",
"title": "Account ID (optional)",
"type": "string"
},
"roles": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"type": "array"
}
Response JSON Schema¶
{
"additionalProperties": false,
"properties": {
"invites": {
"introduced_at": 5,
"items": {
"additionalProperties": false,
"properties": {
"email": {
"description": "The invited email",
"type": "string"
},
"expiration-date": {
"description": "The date when this invite expires, in ISO 8601 format.",
"format": "date-time",
"type": "string"
},
"roles": {
"description": "The roles that this invite's email was invited to join with.",
"items": {
"type": "string"
},
"type": "array"
},
"status": {
"description": "The status of the invite, can be one of Pending, Accepted, Revoked or Expired.",
"enum": [
"Pending",
"Revoked",
"Expired"
],
"type": "string"
}
},
"required": [
"email",
"expiration-date",
"roles",
"status"
],
"type": "object"
},
"type": "array"
},
"store": {
"additionalProperties": false,
"properties": {
"allowed-inclusion-source-stores": {
"description": "The list of store IDs that this store can select snaps from to include in this store.",
"introduced_at": 2,
"items": {
"type": "string"
},
"type": "array"
},
"allowed-inclusion-target-stores": {
"description": "The list of store IDs that can select snaps from this store to include in their stores.",
"introduced_at": 2,
"items": {
"type": "string"
},
"type": "array"
},
"brand-id": {
"description": "The store brand ID or null.",
"type": [
"string",
"null"
]
},
"id": {
"description": "The store ID (slug).",
"type": "string"
},
"manual-review-policy": {
"description": "The review policy for the store (one of: \"allow\", \"avoid\", or \"require\").",
"enum": [
"allow",
"avoid",
"require"
],
"introduced_at": 17,
"type": "string"
},
"name": {
"description": "Visible name of this store.",
"type": "string"
},
"parent": {
"description": "The store parent's ID (slug).",
"type": [
"null",
"string"
]
},
"private": {
"description": "Indicate whether this store will be visible in public lists",
"introduced_at": 17,
"type": "boolean"
},
"roles": {
"description": "The list of available roles in this store.",
"items": {
"type": "string"
},
"type": "array"
},
"snap-name-prefixes": {
"additionalProperties": false,
"introduced_at": 3,
"properties": {
"inheritable": {
"description": "Whether this snap name prefix can be used by child stores or not (default is False).",
"type": "boolean"
},
"parent-id": {
"description": "None if this snap name prefix was defined for the requesting store, else the store ID from which this prefix is being inherited.",
"type": [
"string",
"null"
]
},
"prefix": {
"description": "The snap name prefix.",
"type": "string"
}
},
"required": [
"prefix",
"inheritable",
"parent-id"
],
"type": "object"
},
"store-whitelist": {
"description": "The list of store IDs that this store includes.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id",
"brand-id",
"name",
"roles",
"store-whitelist",
"snap-name-prefixes",
"allowed-inclusion-source-stores",
"allowed-inclusion-target-stores"
],
"type": "object"
},
"users": {
"introduced_at": 1,
"items": {
"additionalProperties": false,
"properties": {
"displayname": {
"description": "The full name",
"type": "string"
},
"email": {
"description": "The email",
"type": "string"
},
"id": {
"description": "The account ID",
"type": "string"
},
"roles": {
"description": "The roles that this user has in this store",
"items": {
"type": "string"
},
"type": "array"
},
"username": {
"description": "The store username",
"type": "string"
}
},
"required": [
"id",
"displayname ",
"email",
"username",
"roles"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"store",
"users",
"invites"
],
"type": "object"
}
Fetch store daily releases feeds¶
Introduced in version 1
- GET /api/v2/stores/(?P<store_id>[\\w_-]+)/feeds/(?P<feed>[\\w\\.-]+)¶
Fetch store daily releases feeds.
The daily releases feed provides a chronological view of the changes (releasing or closing channels) in all snaps included in the context store.
Events refer to snaps uploaded directly to the context store (local), from allowlisted stores (inherited), specifically selected from related stores (included) and the ones available by default on every store (essential).
The release feed format complies with the JsonFeed specification and
store-specific information is provided within the _snap_store
extension.
- reqheader Authorization
macaroon authorization header for a store admin
- status 200
success
- status 400
malformed or unsupported feed name.
- status 401
authentication required
- status 404
store id or feed do not exist or user does not have enough permission to see it
- status 501
daily-store-feeds feature globaly or locally disabled
Usage¶
As mentioned in earlier in this documention, this endpoint can be
exercised using the surl
command as follows:
surl -s production -e your-email@example.com
https://dashboard.snapcraft.io/api/v2/stores/the-store-id/feeds/2018-09-01.json
where your-email@example.com
has to be an existing account with
permission to view the store with ID the-store-id
.
Example¶
Retrieve the release feed for the brand store with id test-feeds
on 2018-09-11
.
Request:
GET /api/v2/stores/test-feeds/feeds/2018-09-11.json HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"version": "https://jsonfeed.org/version/1",
"title": "test-feeds store release feed for 2018-09-11",
"home_page_url": "https://dashboard.snapcraft.io/dev/store/test-feeds/",
"feed_url": "https://dashboard.snapcraft.io/api/v2/stores/test-feeds/feeds/2018-09-11.json",
"next_url": "https://dashboard.snapcraft.io/api/v2/stores/test-feeds/feeds/2018-09-12.json"
"items": [
{
"id": "ea3b4c29-c933-4bd4-8026-0a1884709c22",
"title": "core 16-2.35.1+git944.b5355ba (5475) released on edge for i386"
"date_published": "2018-09-11T04:47:44.497879",
"author": {
"name": "Canonical"
},
"_snap_store": {
"event-type": "release",
"snap-name": "core",
"architecture": "i386",
"channel": {
"track": null,
"risk": "edge",
"branch": null
},
"snap-revision": {
"build-url": "https://launchpad.net/~snappy-dev/+snap/core/+build/330090",
"version": "16-2.35.1+git944.b5355ba",
"revision": 5475
}
}
}
]
}
Errors¶
If the given store ID does not exist, or the authenticated user does not have enough permissions to manage such store, the following error response is returned:
Request:
GET /api/v2/stores/other-store-id/feeds/2018-09-11.json HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 404 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "resource-not-found",
"message": "The resource requested does not exist or credentials are not sufficient to access it."
}
]
}
If there is no feed for the requested date:
Request:
GET /api/v2/stores/the-store-id/feeds/2018-08-11.json HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 404 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "resource-not-found",
"message": "No feed for store \"the-store-id\" on 2018-08-11.",
}
]
}
If the feed name requested does not match YYYY-MM-DD.json
format:
Request:
GET /api/v2/stores/the-store-id/feeds/foo HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "bad-request",
"extra": {
"invalid": "foo"
},
"message": "Malformed feed name, expects: YYYY-MM-DD.json",
}
]
}
If the feed extension is not json
(the only supported extension so far):
Request:
GET /api/v2/stores/the-store-id/feeds/2018-09-11.xml HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "bad-request",
"extra": {
"invalid": "xml"
},
"message": "Only JSON feed is supported.",
}
]
}
If the feed date-part is not a valid ISO8601 date:
Request:
GET /api/v2/stores/the-store-id/feeds/2018-13-32.json HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 400 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "bad-request",
"extra": {
"invalid": "2018-13-31"
},
"message": "Malformed date, must be ISO8601.",
}
]
}
If the store_daily_feeds_api
feature is not globally enabled:
Request:
GET /api/v2/stores/the-store-id/feeds/2018-09-11.json HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 501 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "feature-disabled",
"extra": {
"feature": "store_daily_feeds_api"
},
"message": "Store daily feeds API is disabled"
}
]
}
If the daily_feeds
feature is not enabled for the context store:
Request:
GET /api/v2/stores/the-store-id/feeds/2018-09-11.json HTTP/1.1
Host: dashboard.snapcraft.io
Authorization: Macaroon root=..., discharge=...
Content-Type: application/json
Response:
HTTP/1.1 501 OK
Content-Type: application/json; charset=utf-8
{
"error-list": [
{
"code": "feature-disabled",
"extra": {
"feature": "daily_feeds"
},
"message": "Daily feeds feature is not enabled for this store"
}
]
}
Response JSON Schema¶
{
"additionalProperties": false,
"properties": {
"feed_url": {
"type": "string"
},
"home_page_url": {
"type": "string"
},
"items": {
"items": {
"additionalProperties": false,
"properties": {
"_snap_store": {
"additionalProperties": false,
"properties": {
"architecture": {
"type": "string"
},
"channel": {
"additionalProperties": false,
"properties": {
"branch": {
"type": [
"string",
"null"
]
},
"risk": {
"type": [
"string",
"null"
]
},
"track": {
"type": [
"string",
"null"
]
}
},
"required": [
"track",
"risk",
"branch"
],
"type": "object"
},
"event-type": {
"enum": [
"release"
],
"type": "string"
},
"snap-name": {
"type": "string"
},
"snap-revision": {
"additionalProperties": false,
"properties": {
"build-url": {
"type": [
"string",
"null"
]
},
"revision": {
"type": "number"
},
"version": {
"type": "string"
}
},
"required": [
"revision",
"version",
"build_url"
],
"type": [
"object",
"null"
]
}
},
"required": [
"snap-name",
"snap-revision",
"architecture",
"channel",
"event_type"
],
"type": "object"
},
"author": {
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
},
"date_published": {
"format": "date-time",
"type": "string"
},
"id": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"id",
"title",
"date_published",
"author",
"_snap_store"
],
"type": "object"
},
"minItems": 0,
"type": "array"
},
"next_url": {
"type": "string"
},
"title": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"version",
"title",
"home_page_url",
"feed_url",
"next_url",
"items"
],
"type": "object"
}
Manage store invitations¶
Introduced in version 7
- POST /api/v2/stores/(?P<store_id>[\\w_-]+)/invites¶
- PUT /api/v2/stores/(?P<store_id>[\\w_-]+)/invites¶
Manage store invitations.
- reqheader Authorization
macaroon authorization header for a store admin
- status 200
success
- status 400
error
- status 401
authentication required
- status 404
store id does not exist or user does not have enough permission to see it
Usage¶
As mentioned in the introduction, this endpoint can be exercised using the
surl
command as follows:
Create Invites (POST):
surl -p store_admin -s production -e your-email@example.com -X POST -d '[{"email": "some-email@example.com", "roles": ["view", "access"]}]' https://dashboard.snapcraft.io/api/v2/stores/the-store-id/invites
Modify Invites (PUT):
surl -p store_admin -s production -e your-email@example.com -X PUT -d '[{"email": "some-email@example.com", "action": "revoke"}, {"email": "another-email@example.com", "action": "resend"}]' https://dashboard.snapcraft.io/api/v2/stores/the-store-id/invites
where your-email@example.com
has to be an existing account with
permission to manage the store with ID the-store-id
.
If the authenticating macaroon has any store_ids
attenuation, the store
the-store-id
has to be among the allowed stores in that constraint
(see the --allowed-store
option in surl as a way to get a macaroon
with such attenuation).
Changelog¶
Version 31: Added extra field to store info dict: “brand-id”.
Version 28: Added extra field to store info dict: parent.
Version 19: Update store invites endpoint to allow modifications to existing invites.
Version 17: Added extra fields to store info dict: private, manual-review-policy
Version 7: New API endpoint to create store invites.
Request JSON Schema¶
{
"description": "For POST, the request should contain a list of emails with their roles to invite to the store. For PUT, the request should contain a list of emails with the actions to take.",
"minItems": 1,
"oneOf": [
{
"description": "List of emails with their roles to invite to the store.",
"items": {
"additionalProperties": false,
"properties": {
"email": {
"description": "Email to send the invitation to for joining the store.",
"type": "string"
},
"roles": {
"description": "List of roles to assign to the user once the invitation is accepted.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"email",
"roles"
],
"type": "object"
},
"type": "array"
},
{
"description": "List of invited emails with actions to perform.",
"introduced_at": 19,
"items": {
"additionalProperties": false,
"properties": {
"action": {
"description": "Action to apply to the given email.",
"enum": [
"open",
"resend",
"revoke"
],
"type": "string"
},
"email": {
"description": "Email to send the invitation to for joining the store.",
"type": "string"
}
},
"required": [
"email",
"action"
],
"type": "object"
},
"minItems": 1,
"type": "array"
}
]
}
Response JSON Schema¶
{
"additionalProperties": false,
"properties": {
"invites": {
"introduced_at": 5,
"items": {
"additionalProperties": false,
"properties": {
"email": {
"description": "The invited email",
"type": "string"
},
"expiration-date": {
"description": "The date when this invite expires, in ISO 8601 format.",
"format": "date-time",
"type": "string"
},
"roles": {
"description": "The roles that this invite's email was invited to join with.",
"items": {
"type": "string"
},
"type": "array"
},
"status": {
"description": "The status of the invite, can be one of Pending, Accepted, Revoked or Expired.",
"enum": [
"Pending",
"Revoked",
"Expired"
],
"type": "string"
}
},
"required": [
"email",
"expiration-date",
"roles",
"status"
],
"type": "object"
},
"type": "array"
},
"store": {
"additionalProperties": false,
"properties": {
"allowed-inclusion-source-stores": {
"description": "The list of store IDs that this store can select snaps from to include in this store.",
"introduced_at": 2,
"items": {
"type": "string"
},
"type": "array"
},
"allowed-inclusion-target-stores": {
"description": "The list of store IDs that can select snaps from this store to include in their stores.",
"introduced_at": 2,
"items": {
"type": "string"
},
"type": "array"
},
"brand-id": {
"description": "The store brand ID or null.",
"type": [
"string",
"null"
]
},
"id": {
"description": "The store ID (slug).",
"type": "string"
},
"manual-review-policy": {
"description": "The review policy for the store (one of: \"allow\", \"avoid\", or \"require\").",
"enum": [
"allow",
"avoid",
"require"
],
"introduced_at": 17,
"type": "string"
},
"name": {
"description": "Visible name of this store.",
"type": "string"
},
"parent": {
"description": "The store parent's ID (slug).",
"type": [
"null",
"string"
]
},
"private": {
"description": "Indicate whether this store will be visible in public lists",
"introduced_at": 17,
"type": "boolean"
},
"roles": {
"description": "The list of available roles in this store.",
"items": {
"type": "string"
},
"type": "array"
},
"snap-name-prefixes": {
"additionalProperties": false,
"introduced_at": 3,
"properties": {
"inheritable": {
"description": "Whether this snap name prefix can be used by child stores or not (default is False).",
"type": "boolean"
},
"parent-id": {
"description": "None if this snap name prefix was defined for the requesting store, else the store ID from which this prefix is being inherited.",
"type": [
"string",
"null"
]
},
"prefix": {
"description": "The snap name prefix.",
"type": "string"
}
},
"required": [
"prefix",
"inheritable",
"parent-id"
],
"type": "object"
},
"store-whitelist": {
"description": "The list of store IDs that this store includes.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id",
"brand-id",
"name",
"roles",
"store-whitelist",
"snap-name-prefixes",
"allowed-inclusion-source-stores",
"allowed-inclusion-target-stores"
],
"type": "object"
},
"users": {
"introduced_at": 1,
"items": {
"additionalProperties": false,
"properties": {
"displayname": {
"description": "The full name",
"type": "string"
},
"email": {
"description": "The email",
"type": "string"
},
"id": {
"description": "The account ID",
"type": "string"
},
"roles": {
"description": "The roles that this user has in this store",
"items": {
"type": "string"
},
"type": "array"
},
"username": {
"description": "The store username",
"type": "string"
}
},
"required": [
"id",
"displayname ",
"email",
"username",
"roles"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"store",
"users",
"invites"
],
"type": "object"
}
Request store metrics¶
Introduced in version 15
- POST /api/v2/stores/(?P<store_id>[\\w_-]+)/metrics/models¶
Request store metrics.
- reqheader Authorization
macaroon authorization header for a store admin
- status 200
success
- status 400
error
- status 401
authentication required
- status 404
store id does not exist or user does not have enough permission to see it
Usage¶
As mentioned in the introduction, this endpoint can be exercised using the
surl
command as follows:
Request store metrics (POST):
surl -p store_admin -s production -e your-email@example.com -X POST -d '{"filters":{"month":9 ,"year":2021}}' https://dashboard.snapcraft.io/api/v2/stores/the-store-id/metrics/models
where your-email@example.com
has to be an existing account with
permission to manage the store with ID the-store-id
.
If the authenticating macaroon has any store_ids
attenuation, the store
the-store-id
has to be among the allowed stores in that constraint
(see the --allowed-store
option in surl
as a way to get a macaroon
with such attenuation).
Changelog¶
Version 29: Added extra field to store metrics: “device-ids”.
Version 15: New API endpoint to fetch store metrics.
Request JSON Schema¶
{
"additionalProperties": false,
"properties": {
"filters": {
"additionalProperties": false,
"properties": {
"month": {
"maximum": 12,
"minimum": 1,
"type": "integer"
},
"year": {
"minimum": 2020,
"type": "integer"
}
},
"required": [
"month",
"year"
],
"type": "object"
}
},
"type": "object"
}
Response JSON Schema¶
{
"items": {
"additionalProperties": false,
"properties": {
"brand-id": {
"type": "string"
},
"brand-username": {
"type": "string"
},
"count": {
"type": "number"
},
"device-brand": {
"type": "string"
},
"device-ids": {
"items": {
"type": "string"
},
"type": "array"
},
"device-model": {
"type": "string"
},
"store": {
"type": "string"
}
},
"required": [
"brand-id",
"brand-username",
"count",
"device-brand",
"device-model",
"store"
],
"type": "object"
},
"type": "array"
}
Change store settings¶
Introduced in version 18
- PUT /api/v2/stores/(?P<store_id>[\\w_-]+)/settings¶
Edit Store settings.
- reqheader Authorization
macaroon authorization header for a store admin
- status 200
success
- status 400
error
- status 401
authentication required
- status 404
store id does not exist or user does not have enough permission to see it
Usage¶
As mentioned in the introduction, this endpoint can be exercised using the
surl
command as follows:
surl -p store_admin -s production -e your-email@example.com -X PUT -d '{"manual-review-policy": "allow", "private": true}' https://dashboard.snapcraft.io/api/v2/stores/the-store-id/settings
where your-email@example.com
has to be an existing account with
permission to manage the store with ID the-store-id
.
If the authenticating macaroon has any store_ids
attenuation, the store
the-store-id
has to be among the allowed stores in that constraint
(see the --allowed-store
option in surl as a way to get a macaroon
with such attenuation).
Changelog¶
Version 31: Added extra field to store info dict: “brand-id”.
Version 28: Added extra field to store info dict: parent.
Version 18: New API endpoint to edit store settings.
Request JSON Schema¶
{
"additionalProperties": false,
"introduced_at": 18,
"properties": {
"manual-review-policy": {
"description": "The review policy for the store (one of: \"allow\", \"avoid\", or \"require\").",
"enum": [
"allow",
"avoid",
"require"
],
"type": "string"
},
"private": {
"description": "Indicate whether this store will be visible in public lists",
"type": "boolean"
}
},
"required": [
"manual-review-policy",
"private"
],
"type": "object"
}
Response JSON Schema¶
{
"additionalProperties": false,
"properties": {
"invites": {
"introduced_at": 5,
"items": {
"additionalProperties": false,
"properties": {
"email": {
"description": "The invited email",
"type": "string"
},
"expiration-date": {
"description": "The date when this invite expires, in ISO 8601 format.",
"format": "date-time",
"type": "string"
},
"roles": {
"description": "The roles that this invite's email was invited to join with.",
"items": {
"type": "string"
},
"type": "array"
},
"status": {
"description": "The status of the invite, can be one of Pending, Accepted, Revoked or Expired.",
"enum": [
"Pending",
"Revoked",
"Expired"
],
"type": "string"
}
},
"required": [
"email",
"expiration-date",
"roles",
"status"
],
"type": "object"
},
"type": "array"
},
"store": {
"additionalProperties": false,
"properties": {
"allowed-inclusion-source-stores": {
"description": "The list of store IDs that this store can select snaps from to include in this store.",
"introduced_at": 2,
"items": {
"type": "string"
},
"type": "array"
},
"allowed-inclusion-target-stores": {
"description": "The list of store IDs that can select snaps from this store to include in their stores.",
"introduced_at": 2,
"items": {
"type": "string"
},
"type": "array"
},
"brand-id": {
"description": "The store brand ID or null.",
"type": [
"string",
"null"
]
},
"id": {
"description": "The store ID (slug).",
"type": "string"
},
"manual-review-policy": {
"description": "The review policy for the store (one of: \"allow\", \"avoid\", or \"require\").",
"enum": [
"allow",
"avoid",
"require"
],
"introduced_at": 17,
"type": "string"
},
"name": {
"description": "Visible name of this store.",
"type": "string"
},
"parent": {
"description": "The store parent's ID (slug).",
"type": [
"null",
"string"
]
},
"private": {
"description": "Indicate whether this store will be visible in public lists",
"introduced_at": 17,
"type": "boolean"
},
"roles": {
"description": "The list of available roles in this store.",
"items": {
"type": "string"
},
"type": "array"
},
"snap-name-prefixes": {
"additionalProperties": false,
"introduced_at": 3,
"properties": {
"inheritable": {
"description": "Whether this snap name prefix can be used by child stores or not (default is False).",
"type": "boolean"
},
"parent-id": {
"description": "None if this snap name prefix was defined for the requesting store, else the store ID from which this prefix is being inherited.",
"type": [
"string",
"null"
]
},
"prefix": {
"description": "The snap name prefix.",
"type": "string"
}
},
"required": [
"prefix",
"inheritable",
"parent-id"
],
"type": "object"
},
"store-whitelist": {
"description": "The list of store IDs that this store includes.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"id",
"brand-id",
"name",
"roles",
"store-whitelist",
"snap-name-prefixes",
"allowed-inclusion-source-stores",
"allowed-inclusion-target-stores"
],
"type": "object"
},
"users": {
"introduced_at": 1,
"items": {
"additionalProperties": false,
"properties": {
"displayname": {
"description": "The full name",
"type": "string"
},
"email": {
"description": "The email",
"type": "string"
},
"id": {
"description": "The account ID",
"type": "string"
},
"roles": {
"description": "The roles that this user has in this store",
"items": {
"type": "string"
},
"type": "array"
},
"username": {
"description": "The store username",
"type": "string"
}
},
"required": [
"id",
"displayname ",
"email",
"username",
"roles"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"store",
"users",
"invites"
],
"type": "object"
}