Skip to content

Feeds and directories from a script

An administrator makes feeds and asset directories on Admin → Feeds and Admin → Assets. This page is about doing the same from a script: setting up a new server the same way every time, or giving a new team its own directory, without anybody at a browser.

What it takes

The same key as for service tokens from a script: a service token with the admin scope, sent as X-ApiKey, the password of Basic authentication, or a Bearer token. A super administrator makes that one on the tokens page.

The API follows the same rules as the pages, because both call the same code. What the create form refuses, the API refuses, and every change lands in the audit log under the same name.

Make a feed

$admin = @{ 'X-ApiKey' = $adminKey }
$feed = Invoke-RestMethod -Method Post "https://packages.example/api/management/feeds" -Headers $admin `
    -ContentType 'application/json' -Body (@{
        name    = 'modules'
        purpose = 'powershell'   # any, powershell, nuget or chocolatey
        gallery = $true          # proxy the PowerShell Gallery from the start
    } | ConvertTo-Json)

$feed.url   # https://packages.example/nuget/modules/v3/index.json
Field Meaning
name Letters, digits, dot, dash or underscore, starting with a letter or digit, at most 64. Required.
kind package (the default) or directory.
purpose any (the default), powershell, nuget or chocolatey. A push of the wrong kind of package is refused.
gallery With a purpose: the matching public gallery becomes the feed's upstream, so it is a proxy feed from the first request.
anonymousRead Anyone can read without a key. Off by default.
allowOverwrite Pushing an existing version replaces it, instead of 409.
onDelete unlist (the default) or delete: what a client's delete does.

Make an asset directory

Invoke-RestMethod -Method Post "https://packages.example/api/management/feeds" -Headers $admin `
    -ContentType 'application/json' -Body (@{
        name          = 'installers'
        kind          = 'directory'
        anonymousRead = $true     # anyone downloads a file whose path they know
        anonymousList = $false    # but only key holders browse the folders
    } | ConvertTo-Json)

On a server with a shares mount (FiGet:Assets:SharesRoot), a directory can serve one of the shares as it is:

-Body (@{ name = 'intune'; kind = 'directory'; folder = @{ share = 'intune'; inside = 'scripts'; writes = $false } } | ConvertTo-Json)

share is the name of a folder directly under the mount, inside a folder within it. Anything that is not on the mount right now is refused; a path is never accepted, and the answers never show one. Move a directory later with PUT .../feeds/{name}/folder, and back to FiGet's own storage with an empty share.

Change, rename, delete

# Only what is sent changes.
Invoke-RestMethod -Method Patch "https://packages.example/api/management/feeds/modules" -Headers $admin `
    -ContentType 'application/json' -Body (@{ anonymousRead = $true; allowedNetworks = @('10.0.0.0/8') } | ConvertTo-Json)

# The old name keeps working for clients that still use it.
Invoke-RestMethod -Method Post "https://packages.example/api/management/feeds/modules/rename" -Headers $admin `
    -ContentType 'application/json' -Body (@{ name = 'ps-modules'; keepOldName = $true } | ConvertTo-Json)

# The name is repeated on purpose: a script whose variable came out empty deletes nothing.
Invoke-RestMethod -Method Delete "https://packages.example/api/management/feeds/ps-modules?confirm=ps-modules" -Headers $admin

allowedNetworks is a list of addresses and CIDR ranges the feed may be reached from; [] opens it to any address again. If one entry is not valid, nothing in the request is applied. A delete takes everything the feed or directory holds, files included.

GET .../feeds lists them all and GET .../feeds/{name} shows one with its alternate names.

Upstreams

A package feed asks its upstreams, in order, for what it does not hold. Adding the first one makes it a proxy feed.

$upstreams = "https://packages.example/api/management/feeds/modules/upstreams"

# A public gallery by name: it supplies the address and the protocol.
Invoke-RestMethod -Method Post $upstreams -Headers $admin -ContentType 'application/json' `
    -Body (@{ name = 'gallery'; gallery = 'PowerShell Gallery' } | ConvertTo-Json)

# A server of your own, asked only for your own packages, with a password from the environment.
$internal = Invoke-RestMethod -Method Post $upstreams -Headers $admin -ContentType 'application/json' -Body (@{
        name          = 'internal'
        url           = 'https://nuget.internal.example/v3/index.json'
        allow         = '^Contoso\.'
        credentialRef = 'FIGET_UPSTREAM_INTERNAL'
    } | ConvertTo-Json)

# Ask it first, then switch it off for a while.
Invoke-RestMethod -Method Post "$upstreams/$($internal.id)/move" -Headers $admin -ContentType 'application/json' `
    -Body (@{ direction = 'up' } | ConvertTo-Json)
Invoke-RestMethod -Method Patch "$upstreams/$($internal.id)" -Headers $admin -ContentType 'application/json' `
    -Body (@{ enabled = $false } | ConvertTo-Json)
Field Meaning
name Unique within the feed. Required.
gallery nuget.org, PowerShell Gallery or Chocolatey community, instead of url and protocol.
url, protocol Where the upstream is, and v3 (the default) or v2. An http or https address; an address written as an IP on the link-local or reserved blocks is refused. A host name is yours to vouch for: it is not checked.
allow, deny Regular expressions, one per line, matched anywhere in the package id and ignoring case: only ids that match, or never these. Anchor with ^ and $ for a prefix or a whole id.
credentialRef The name of an environment variable holding the password, starting with FIGET_UPSTREAM_. The password itself is never sent to FiGet's API. "" removes it.
enabled Off: the upstream stays configured but is not asked.

An upstream is addressed by its id, from the list or from the answer that made it. DELETE .../upstreams/{id} removes it with the listings cached from it; the packages already cached stay in the feed.

Not here yet

Who may do what on a feed, retention and the install instructions are still set on the pages.

What is refused, and how

Answer When
401 No key was sent, or what was sent as a Basic password or Bearer token is not a valid key.
403 A key that is not valid sent as X-ApiKey; a valid key that is not a service token with the admin scope.
400 The body is not JSON of the shape above, a value is not one of those listed, a share is not on the mount, or a delete without ?confirm=. The error field says which.
404 No feed or directory has that name, or alternate name.
409 The name is taken by a feed, a directory, an alternate name, or another upstream of the feed; a folder that cannot be chosen here; an upstream on an asset directory.