instagram-publish
Publish a photo, video, Reel, carousel or Story on Instagram with the Graph API, in one call.
Prerequisites
- An Instagram Business or Creator account.
- A Meta app with the Instagram product (developers.facebook.com/apps). The app can stay in Development mode if you only post on your own account.
- Media must be public HTTPS URLs (Instagram downloads them itself): S3, your server, etc.
Get a token and an account id
Two ways, depending on how you authenticate. Option A is simpler and is the default.
Option A: Instagram Login (default, --login instagram)
No Facebook Page needed. The token is valid 60 days.
- In the app dashboard: Roles → Roles, add your Instagram account as Instagram Tester.
- Accept the invite on instagram.com/accounts/manage_access (tab Tester Invites).
- In the dashboard: Instagram → API setup with Instagram business login → Add account → Generate token.
- The account id shown next to it is your
IG_USER_ID.
Official guide: Instagram API with Instagram Login.
Refresh the token (Option A only)
A token expires after 60 days. Refresh it before that: it must be older than 24h and not expired, and each refresh gives 60 more days. Run it about once a month.
from instagram_publish import InstagramClient
ig = InstagramClient() # uses your current IG_ACCESS_TOKEN
result = ig.refresh_token() # calls Meta
new_token = result["access_token"] # save it: file, database, secret manager...
print(result["expires_in"] // 86400) # days of validity (about 60)
After a refresh, ig itself uses the new token right away. To keep it for next time, pass token_file: the client then reads the token from that file and rewrites it (mode 600) on every refresh.
ig = InstagramClient(token_file="~/.ig_token") # first run: seeded from IG_ACCESS_TOKEN if the file doesn't exist yet
ig.refresh_token() # new token saved in ~/.ig_token
Once the file exists it wins over access_token and IG_ACCESS_TOKEN. Without token_file, refresh_token() does not store anything: save new_token yourself (an environment variable cannot be updated durably from Python).
From the shell (or a monthly cron), with a token file nothing else is needed:
instagram-publish --token-file ~/.ig_token refresh-token
instagram-publish --token-file ~/.ig_token publish https://example.com/photo.jpg
Without --token-file, refresh-token prints the new token and you must save it yourself.
Option B: Facebook Login (--login facebook)
For when you already have a Facebook Login token (e.g. from the Graph API Explorer). Requires an Instagram account linked to a Facebook Page and the permissions instagram_basic, instagram_content_publish, pages_show_list. Explorer tokens last about 1 hour and cannot be refreshed with refresh-token.
IG_USER_ID is the instagram_business_account.id returned by GET me/accounts?fields=instagram_business_account.
What --login does
It only tells the tool which server your token belongs to: instagram → graph.instagram.com, facebook → graph.facebook.com. A token only works on the server that issued it, so a wrong value gives an auth error. In Python: InstagramClient(login="facebook"). In the CLI, put it before the subcommand: instagram-publish --login facebook publish <url>.
Install
pip install instagram-publish # or: uvx instagram-publish ...
export IG_ACCESS_TOKEN="..."
export IG_USER_ID="..."
Env vars are the default; you can also pass access_token= and user_id= to InstagramClient.
Usage
from instagram_publish import InstagramClient
ig = InstagramClient() # reads IG_ACCESS_TOKEN and IG_USER_ID
ig.publish("https://example.com/photo.jpg", "My caption") # photo
ig.publish("https://example.com/clip.mp4", "My Reel") # video -> Reel
ig.publish(["https://example.com/1.jpg", "https://example.com/2.mp4"]) # carousel (2-10 items)
ig.publish("https://example.com/photo.jpg", story=True) # story (no caption)
CLI:
instagram-publish publish https://example.com/photo.jpg -c "My caption"
instagram-publish publish https://example.com/photo.jpg --story
Options: --ai-generated flags the post as AI-generated, --api-version overrides the Graph API version (default v26.0), --login picks the token flavour (see above).
Errors raise InstagramError.
Security notes
- The token is sent in an
Authorizationheader and is never put in error messages. The one exception isrefresh_token(): Meta's refresh endpoint takes the token as a URL parameter, so that URL can appear in debug logs (urllib3) or proxy logs. Keep debug logging off in production. token_fileis written atomically with mode 600. If it is readable by other users you get a warning. Once it exists it wins overaccess_tokenandIG_ACCESS_TOKEN: delete it if you want to switch to a new token.- Media must be
https://URLs; Instagram fetches them itself, so never pass URLs you don't control or trust. Redirects are not followed.api_versionandIG_USER_IDare validated. - Proxy variables (
HTTPS_PROXY,REQUESTS_CA_BUNDLE) are honoured: the token goes through them. - Publishing is not retried automatically, to avoid duplicate posts. If
publish()times out, check your profile before retrying.
Migrating from 0.x
1.0 is a rewrite: config.py, Filestack and local file paths are gone (media are public HTTPS URLs), and the entry point is InstagramClient(...).publish(...).
Development
uv run pytest
Metadata
Release files for instagram-publish 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| instagram_publish-1.0.0.tar.gz | 10.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| instagram_publish-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 20.3 kB
Release files / instagram_publish-1.0.0.tar.gz
| Download URL | instagram_publish-1.0.0.tar.gz |
|---|---|
| Size | 10.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
238e06fdf45c810d104d01720601a47c8cb7d4aba8c6c2db926c4eb274bfdcdb
|
|
BLAKE2b-256 checksum How to use checksums |
370fc59428420038e522e6af54eabbe8e7842c5d97dd9b8ad8ff4701cf931b91
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / instagram_publish-1.0.0-py3-none-any.whl
| Download URL | instagram_publish-1.0.0-py3-none-any.whl |
|---|---|
| Size | 10.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
66efdab45240d8785bcd700751233c0ce53d997c8e3e9437479df5ce4f8a456c
|
|
BLAKE2b-256 checksum How to use checksums |
358719a042e2ca55f2b09ab2b72239dfb2d45a241aa4eb631f9f1616444ea447
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for instagram-publish 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.