AI and agents
Attach SocialCutter output images to Airtable records
Generate the formats with SocialCutter and attach the output URL to an attachment field through the Airtable API: direct flow, Python, curl and errors.
- Airtable
- attachment
- API
- personal access token
- Python
- curl
- SocialCutter
Why the flow is direct in Airtable
An attachment field (multipleAttachments) accepts, on write, a list of objects with a url. Airtable downloads the file from that URL and keeps its own copy. Since SocialCutter returns one public URL per generated format, there is no need to upload binaries or build an intermediary: you generate, you attach, done.
The boundary is the same as in every other integration: SocialCutter generates images, it does not publish. The crop is centred, with no subject detection and no content analysis. Publishing or attaching is the caller’s job.
Why pre-generate before attaching
Airtable shows the attachment thumbnail, but it does not crop the image to the ratio each slot asks for. If you store a single master and reuse it for the gallery thumbnail, a reel brief and the record cover, every view scales it its own way.
| Slot in the record | SocialCutter destination | Size |
|---|---|---|
| Square gallery thumbnail | instagram post | 1080x1080 (1:1) |
| Vertical image for a reel brief | instagram story | 1080x1920 (9:16) |
| Landscape card or gallery view | twitter post | 1200x675 (16:9) |
| Record or view cover | facebook post | 1200x630 (1.91:1) |
| Wide banner header | linkedin cover | 1128x191 (5.9:1) |
Pre-generating those five costs 5 uses and comes back in a single request, so the record ends up complete with every size already resolved.
How the API writes to an attachment field
- Write shape: an array of objects.
urlis enough;filenameis optional but recommended so you control the attachment name. - Airtable downloads the file. The URL has to be reachable from outside, with no login and no expired signature, and it has to return an image
Content-Type. - What you send is what stays. Attachments you leave out of the array are removed from the field. To keep them, send them again with their
id: the object the read returns works as is. - On read, URLs come from
v5.airtableusercontent.comand expire after two hours. They are for downloading, not for embedding on another site. - Plan limits: up to 5 GB per file, with per-base attachment storage ranging from 1 GB on Free to 1 TB on Enterprise. The full reference is at https://airtable.com/developers/web/api/field-model.
Create the personal access token
Go to https://airtable.com/create/tokens, add the data.records:write scope (plus schema.bases:read if you want to list the table’s fields) and grant access to the specific base. It goes in the Authorization: Bearer pat... header. Keep the token and the base id in environment variables, never in the code.
Direct flow with Python
import os
import requests
SC = "https://api.socialcutter.theboomer.dev"
BASE = os.environ["AIRTABLE_BASE_ID"]
TABLE = os.environ["AIRTABLE_TABLE_ID"]
sc = requests.Session()
sc.headers["X-API-Key"] = os.environ["SOCIALCUTTER_API_KEY"]
at = requests.Session()
at.headers["Authorization"] = f"Bearer {os.environ['AIRTABLE_TOKEN']}"
# 1. Generate every format in one request
resp = sc.post(f"{SC}/api/v1/images/process", json={
"source": {"type": "url", "value": "https://example.com/master.jpg"},
"destinations": [
{"platform": "instagram", "format": "post"},
{"platform": "instagram", "format": "story"},
{"platform": "twitter", "format": "post"},
],
}, timeout=60)
resp.raise_for_status()
outputs = resp.json()["outputs"]
# 2. Attach the outputs: Airtable downloads and rehosts them
files = [
{"url": out["url"], "filename": f"{out['platform']}-{out['format']}.webp"}
for out in outputs
]
r = at.patch(f"https://api.airtable.com/v0/{BASE}/{TABLE}/{os.environ['RECORD_ID']}",
json={"fields": {"Attachments": files}}, timeout=60)
r.raise_for_status()
for att in r.json()["fields"]["Attachments"]:
print(att["filename"], att["size"], att["type"])
# 3. Create a new record with the square image already attached
new = at.post(f"https://api.airtable.com/v0/{BASE}/{TABLE}", json={
"records": [{"fields": {
"Name": "September campaign",
"Attachments": [{"url": outputs[0]["url"], "filename": "instagram-post.webp"}],
}}],
}, timeout=60)
new.raise_for_status()
The batch endpoints take 10 records per request, and it pays to space them out so you stay under 5 requests per second per base.
Direct flow with curl
curl -s -X PATCH "https://api.airtable.com/v0/$BASE_ID/$TABLE_ID/$RECORD_ID" \
-H "Authorization: Bearer $AIRTABLE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"fields\":{\"Attachments\":[{\"url\":\"$OUTPUT_URL\",\"filename\":\"instagram-post.webp\"}]}}" \
| python3 -m json.tool
Replace $BASE_ID with app..., $TABLE_ID with tbl... and $RECORD_ID with rec.... For the field you can use its fld... id instead of the name, which is the most stable choice if someone renames the column.
When Airtable cannot download the URL
If the field stays empty and the response talks about a failed upload, the usual cause is that Airtable could not download the file. Check that the URL is public, that it does not depend on a session, and that it returns the image with its Content-Type. The UI warning is normally “Couldn’t upload. Try adding again” with a 403 from Airtable’s upload domain behind it; support documents it at https://support.airtable.com/docs/attachment.
When the URL cannot be exposed, direct upload remains:
curl -s -X POST \
"https://content.airtable.com/v0/$BASE_ID/$RECORD_ID/$FIELD_ID/uploadAttachment" \
-H "Authorization: Bearer $AIRTABLE_TOKEN" \
-H "Content-Type: application/json" \
--data '{"contentType":"image/webp","file":"<base64>","filename":"instagram-post.webp"}'
That endpoint takes up to 5 MB per file, exactly the same ceiling as the master SocialCutter accepts, so the fallback covers the same range as the URL flow.
Cost
- 1 use per destination (platform and format) per SocialCutter request. Repeated destinations are not charged twice and failed items are refunded.
- Plans: €0 (3 uses/day), €3 (10/day), €9 (30/day) and €29 (100/day), all with API and MCP access.
- The Airtable API is not billed per call, but it does count against your plan’s monthly call limit (1,000 calls per month on Free).
Common errors
| Code | Origin | Meaning |
|---|---|---|
| 400 | SocialCutter | Invalid payload: unknown platform or format |
| 401 | SocialCutter | The X-API-Key header is missing or the key is wrong |
| 413 | SocialCutter | The master is over 5 MB |
| 429 | SocialCutter | Quota exhausted: check GET /api/v1/wallet |
| 401 | Airtable | Token missing, malformed or without access to that base |
| 403 | Airtable | Airtable could not download the attachment URL |
| 404 | Airtable | The base, table or record does not exist |
| 422 | Airtable | Unknown field, or a value that does not fit the attachment type |
| 429 | Airtable | More than 5 requests per second per base: wait around 30 seconds |
Next steps
- Code path: Process images with the SocialCutter API from Python and from the terminal with curl
- No-code: Automate image resizing with n8n, with Zapier and with Make
- Big picture: Automating social media images: the 4 real paths
- SocialCutter API reference: https://docs.socialcutter.theboomer.dev
- Airtable attachment field: https://airtable.com/developers/web/api/field-model
Frequently asked questions
Does Airtable keep the URL or the file?
The file. When you write a URL into an attachment field, Airtable downloads it and rehosts its own copy, so the SocialCutter URL does not have to stay online after the write.
Can I send the binary instead of a URL?
Yes, with the uploadAttachment endpoint, which takes the file as base64 up to 5 MB. Above that size you have to go through a public URL, which is exactly what SocialCutter returns.
What are the Airtable API limits?
5 requests per second per base on every plan, with a maximum of 10 records per request on the batch endpoints. Going over returns 429 and you should wait around 30 seconds before retrying.
What happens to attachments already in the field?
When you write, the list you send is what stays: attachments you leave out are removed. To keep them, send them again in the same array with their id, exactly as the read returned them.
What does preparing one record's formats cost?
1 use per destination, meaning each platform and format pair. Plans are €0, €3, €9 and €29 per month and all of them include API and MCP access.