API documentation

Best practices when using the Packagist.org API

  • If you do scheduled jobs, avoid running things at midnight or once an hour at XX:00. Most people do so and we do see traffic peaks every hour. Pick a "random" time by hand for your cron jobs, or even better (if you can) is to make it run on a really randomized schedule.
  • Send a User-Agent header with all your requests including an email or twitter or some sort of contact information so we can reach out to you if we have an issue with the way you use the API. If not you can leave us with no choice but to block IPs which we'd rather not do.
  • If you send requests in parallel, be a good citizen and do a maximum of 10 concurrent requests, or up to 20 if you only fetch static files. Huge bursts in requests can cause issues for other regular users so try to spread out the load.
  • Use an HTTP/2-capable client and make sure you enable that, it is much more efficient than reconnecting for every request.

Listing package names

All packages

GET https://packagist.org/packages/list.json

{
  "packageNames": [
    "[vendor]/[package]",
    ...
  ]
}

Working example: https://packagist.org/packages/list.json

List packages by organization

GET https://packagist.org/packages/list.json?vendor=[vendor]

{
  "packageNames": [
    "[vendor]/[package]",
    ...
  ]
}

Working example: https://packagist.org/packages/list.json?vendor=composer

List packages by type

GET https://packagist.org/packages/list.json?type=[type]

{
  "packageNames": [
    "[vendor]/[package]",
    ...
  ]
}

Working example: https://packagist.org/packages/list.json?type=composer-plugin

Retrieving additional data if the name is not enough

If you need more than just package names you can additionally request one or more fields to be returned with every package. Currently supported fields are: repository, type, and abandoned.

Note that when requesting fields, the response structure is different.

GET https://packagist.org/packages/list.json?vendor=[type]&fields[]=type&fields[]=repository&fields[]=abandoned

{
  "package": {
    "[vendor]/[package]": {
      "type": "library",
      "repository": "https://...",
      "abandoned": false|true|string (with advised replacement)
    },
    ...
  }
}

Working example: https://packagist.org/packages/list.json?vendor=composer&fields[]=repository&fields[]=type

If you need to retrieve the most popular (this is sorted by downloads over the last week, not overall downloads to ensure we demote formerly-popular packages) packages please use this endpoint to avoid having to request the download stats for all of our 300K+ packages individually. This also includes faver count (github stars + packagist.org favorites) and downloads count. The API is paginated if you need more than 100.

GET https://packagist.org/explore/popular.json?per_page=100

{
  "packages": [
    {
      "name": "[vendor]/[package]",
      "description": "[description]",
      "url": "https://packagist.org/packages/[vendor]/[package]",
      "downloads": 123456,
      "favers": 123
    },
    ...
  ],
  "total": 123456,
  "next": "https://packagist.org/explore/popular.json?page=2&per_page=100"
}

Working example: https://packagist.org/explore/popular.json?per_page=100

Searching for packages

Search results are paginated and you can change the pagination step by using the per_page parameter. For example https://packagist.org/search.json?q=[query]&per_page=5

Search packages by name

GET https://packagist.org/search.json?q=[query]

{
  "results" : [
    {
      "name": "[vendor]/[package]",
      "description": "[description]",
      "url": "https://packagist.org/packages/[vendor]/[package]",
      "repository": [repository url],
      "downloads": [number of downloads],
      "favers": [number of favers]
    },
    ...
  ],
  "total": [number of results],
  "next": "https://packagist.org/search.json?q=[query]&page=[next page number]"
}

Working example: https://packagist.org/search.json?q=monolog

Search packages by tag

GET https://packagist.org/search.json?tags=[tag]

{
  "results": [
    {
      "name": "[vendor]/[package]",
      "description": "[description]",
      "url": "https://packagist.org/packages/[vendor]/[package]",
      "repository": "[repository url]",
      "downloads": [number of downloads],
      "favers": [number of favers]
    }
    ...
  ],
  "total": [numbers of results]
}

Working example: https://packagist.org/search.json?q=monolog&tags=psr-3

Search packages by type

GET https://packagist.org/search.json?q=[query]&type=symfony-bundle

{
  "results" : [
    {
      "name": "[vendor]/[package]",
      "description": "[description]",
      "url": "https://packagist.org/packages/[vendor]/[package]",
      "repository": [repository url],
      "downloads": [number of downloads],
      "favers": [number of favers]
    },
    ...
  ],
  "total": [number of results],
  "next": "https://packagist.org/search.json?q=[query]&page=[next page number]"
}

Working example: https://packagist.org/search.json?q=monolog&type=symfony-bundle

Getting package data

Using the Composer v2 metadata

This is the preferred way to access the data as it is always up to date, and dumped to static files so it is very efficient on our end.

You can also send If-Modified-Since headers to limit your bandwidth usage and cache the files on your end with the proper filemtime set according to our Last-Modified header.

There are a few gotchas though with using this method:

GET https://repo.packagist.org/p2/[vendor]/[package].json

{
  "packages": {
    "[vendor]/[package]": [
      {
        "name": "[vendor]/[package],
        "description": [description],
        "version": "[version1]",
        // ...
      },
      {
        "version": "[version2]",
        // ...
      }
      // ...
    ]
  },
  "minified": "composer/2.0"
}

Working examples:

Looking to remain up to date and know when packages updated? See the Track package updates API.

Using the Composer v1 metadata (DEPRECATED)

You can also send If-Modified-Since headers to limit your bandwidth usage and cache the files on your end with the proper filemtime set according to our Last-Modified header.

There are a few gotchas though with using this method:

  • It only provides you with the package metadata but not information about the maintainers, download stats or github info.
  • It contains providers information which must be ignored but can appear confusing at first. This will disappear in the future though.
  • It does not provide all packages due to the deprecation of the v1 metadata. See the announcement for details.

GET https://repo.packagist.org/p/[vendor]/[package].json

{
  "packages": {
    "[vendor]/[package]": {
      "[version1]": {
        "name": "[vendor]/[package],
        "description": [description],
        // ...
      },
      "[version2]": {
        // ...
      }
      // ...
    }
  }
}

Working example: https://repo.packagist.org/p/monolog/monolog.json

Using the API

The JSON API for packages gives you all the infos we have including downloads, dependents count, github info, etc. However it is generated dynamically so for performance reason we cache the responses for twelve hours. As such if the static file endpoint described above is enough please use it instead.

GET https://packagist.org/packages/[vendor]/[package].json

{
  "package": {
    "name": "[vendor]/[package],
    "description": [description],
    "time": [packagist package creation datetime],
    "maintainers": [list of maintainers],
    "versions": [list of versions and their dependencies, the same data of composer.json]
    "type": [package type],
    "repository": [repository url],
    "downloads": {
      "total": [numbers of download],
      "monthly": [numbers of download per month],
      "daily": [numbers of download per day]
    },
    "favers": [number of favers]
  }
}

Working example: https://packagist.org/packages/monolog/monolog.json

Get package download stats

If you need complete package information and use the JSON API already then please use the downloads key to retrieve stats from that response.

However if you are only interested in download stats for a set of package names, you can use the stats endpoint which includes overall download stats + the faver count (github stars + packagist.org favorites):

GET https://packagist.org/packages/[vendor]/[package]/stats.json

{
  "downloads": {
    "total": 100,
    "monthly": 10,
    "daily": 1
  },
  "versions": [
    "[list of versions]",
    "[list of versions]"
  ],
  "date": "2022-09-15" # stats collection start date
}

Working example: https://packagist.org/monolog/monolog/stats.json

Track package updates

Track package updates

This endpoint provides you with a feed of metadata changes you can poll to know what packages you need to update.

First to initialize this you can poll the API without timestamp to get the most current timestamp, or make your own by using 10000 * time():

GET https://packagist.org/metadata/changes.json

{
  "error": "Invalid or missing "since" query parameter, make sure you store the timestamp at the initial point you started mirroring, then send that to begin receiving changes, e.g. https://packagist.org/metadata/changes.json?since=16142636710498 for example.",
  "timestamp": 16142636710498
}

Working example: https://packagist.org/metadata/changes.json

After that, you should store the timestamp for the next time you want to call the API, let's say 10 minutes later you want to know what changed, you call this again but this time you pass the previous timestamp:

GET https://packagist.org/metadata/changes.json?since=16142636710498

{
  "actions": [
    {
      "type": "update",
      "package": "acme/package",
      "time": 1614264954
    },
    {
      "type": "update",
      "package": "foo/bar~dev",
      "time": 1614264951
    },
    {
      "type": "delete",
      "package": "acme/gone",
      "time": 1614264953
    }
  ]
}

Working example: https://packagist.org/metadata/changes.json?since=17108153370000

In the example above, you receive 3 changes, let's go over what they mean and what you should do to sync these up:

  • acme/update was updated (tagged releases of acme/update), you can fetch https://repo.packagist.org/p2/acme/update.json and should ensure that the Last-Modified is AT LEAST (>=) equal to the time value. If it is older than that, wait a few seconds and retry. Due to internal mirroring delays it may happen that you get a race condition and get an outdated file.
  • foo/bar~dev was updated (dev releases of foo/bar, you can fetch https://repo.packagist.org/p2/foo/bar~dev.json and should ensure that the Last-Modified is AT LEAST (>=) equal to the time value.
  • acme/gone was deleted, you can delete it on your end as well, this means both acme/gone and acme/gone~dev are deleted.

Warning: The changes log is kept for up to 24h on our end, so make sure you fetch the API at least once a day or you will get a resync response like the following:

GET https://packagist.org/metadata/changes.json?since=16140636710498

{
  "actions": [
    {
      "type": "resync",
      "package": "*",
      "time": 1614264954
    }
  ]
}

If you get this, you should assume your data is stale and you should revalidate everything (if you cached files using Last-Modified headers, you can still keep that and make sure with If-Modified-Since requests for every file that it is still up to date).

Get statistics

Get statistics

This endpoint provides basic some statistics.

GET https://packagist.org/statistics.json

{
  "totals": {
    "downloads": [numbers of download]
  }
}

Working example: https://packagist.org/statistics.json

List security advisories

List security advisories

This endpoint provides a list of security advisories. Either a list of packages as query or request parameter or a timestamp as updatedSince query parameter need to be passed.

When querying with a list of packages, known packages will be included in the response with an empty array if they don't have any listed vulnerability. Package names which are not known to have no vulnerability will not be included at all to show that we do not have data on those.

GET https://packagist.org/api/security-advisories/?updatedSince=[timestamp]&packages[]=[vendor/package]

{
  "advisories": {
    "[vendor]/[package]": [
      {
        "advisoryId": "[unique id]",
        "packageName": "[vendor]/[package]",
        "remoteId": "[deprecated, use sources instead]",
        "title": "[title]",
        "link": "[url to issue disclosure]",
        "cve": "[cve if available]",
        "affectedVersions": [affected version in form of a composer constraint]",
        "source": "[deprecated, use sources instead]",
        "sources": [
            {
                "name": "[Name of the source where the advisory was found e.g. GitHub or FriendsOfPHP/security-advisories]"
                "remoteId": "[A reference to identify the advisory in the source e.g. the GitHub advisory id]"
            }
        ],
        "reportedAt": "[date the issue was reported]",
        "composerRepository": "[composer repository the package can be found in]",
        "severity": [severity if available, following the CVSS3 spec]
      }
    ]
  }
}

Working example: https://packagist.org/api/security-advisories/?packages[]=monolog/monolog

Create a package

Create a package

This endpoint creates a package for a specific repo. Parameters username and apiToken are required. Only POST method is allowed.

POST https://packagist.org/api/create-package?username=[username]&apiToken=[apiToken] -d '{"repository":{"url":"[url]"}}'

{
  "status": "success"
}

Working example: curl -X POST 'https://packagist.org/api/create-package?username=zqfan&apiToken=********' -d '{"repository":{"url":"https://github.com/monolog/monolog"}}'