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:

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"
}