{"openapi":"3.1.0","info":{"title":"upload.ad API","version":"1.0.0","description":"The upload.ad REST API (v1) lets you upload creatives to Facebook or TikTok, track upload jobs, and manage ad copy variants. Requests and responses are JSON, except POST /api/v1/uploads, which takes multipart/form-data. Timestamps are ISO 8601 strings in UTC. Errors raised before a route runs (authentication, subscription, permission, and rate-limit checks) use the envelope { \"error\": { \"code\", \"message\" } }; errors from within a route are a plain { \"message\" }."},"servers":[{"url":"https://upload.ad"}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/me":{"get":{"operationId":"getMe","summary":"Get the authenticated account","description":"Returns the authenticated account. Useful as a connectivity and key check.","tags":["Account"],"responses":{"200":{"description":"The authenticated account.","content":{"application/json":{"schema":{"type":"object","required":["account"],"properties":{"account":{"$ref":"#/components/schemas/Account"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/uploads":{"get":{"operationId":"listUploads","summary":"List upload jobs","description":"List upload jobs, newest first.","tags":["Uploads"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum results, default 100.","schema":{"type":"integer","minimum":1,"maximum":200,"default":100}}],"responses":{"200":{"description":"The upload jobs.","content":{"application/json":{"schema":{"type":"object","required":["uploads"],"properties":{"uploads":{"type":"array","items":{"$ref":"#/components/schemas/Upload"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"createUploads","summary":"Upload files","description":"Upload one or more files as multipart/form-data with one or more `files` parts. Each file becomes a background job that pushes it to your Facebook or TikTok ad account, chosen with the `platform` field. Limits per request: up to 25 files, images up to 30 MB, videos up to 200 MB, content type must be image/* or video/*. Requires an active subscription.","tags":["Uploads"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["files"],"properties":{"files":{"type":"array","description":"One or more files, sent as repeated `files` parts. Image or video only.","items":{"type":"string","contentEncoding":"binary"}},"platform":{"type":"string","enum":["meta","tiktok"],"default":"meta","description":"Which ad account to push the files to. Defaults to meta (Facebook)."},"folderId":{"type":"string","description":"Optional library folder to file the resulting creatives under."}}}}}},"responses":{"201":{"description":"One upload job per file, all in processing state.","content":{"application/json":{"schema":{"type":"object","required":["uploads"],"properties":{"uploads":{"type":"array","items":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","description":"Upload id."},"status":{"type":"string","enum":["processing"]}}}}}}}}},"400":{"description":"Missing files, too many files, unsupported file type, or a file over the size limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"An active subscription is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthError"}}}}}}},"/api/v1/uploads/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Upload id.","schema":{"type":"string"}}],"get":{"operationId":"getUpload","summary":"Get an upload job","description":"Fetch a single upload job.","tags":["Uploads"],"responses":{"200":{"description":"The upload job.","content":{"application/json":{"schema":{"type":"object","required":["upload"],"properties":{"upload":{"$ref":"#/components/schemas/Upload"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Upload not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"retryUpload","summary":"Retry a failed upload","description":"Retry a failed upload. The job returns to processing and runs again.","tags":["Uploads"],"responses":{"200":{"description":"The job is processing again.","content":{"application/json":{"schema":{"type":"object","required":["upload"],"properties":{"upload":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","description":"Upload id."},"status":{"type":"string","enum":["processing"]}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No failed upload with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"dismissUpload","summary":"Dismiss an upload job","description":"Dismiss a completed or failed upload job. This removes the job from the upload list; creatives created by the job are unaffected.","tags":["Uploads"],"responses":{"200":{"description":"The job was removed.","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Upload not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The upload is still processing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/creatives":{"get":{"operationId":"listCreatives","summary":"List creatives","description":"List creatives, newest first.","tags":["Creatives"],"responses":{"200":{"description":"The creatives.","content":{"application/json":{"schema":{"type":"object","required":["creatives"],"properties":{"creatives":{"type":"array","items":{"$ref":"#/components/schemas/Creative"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"delete":{"operationId":"deleteCreatives","summary":"Delete creatives","description":"Delete creatives by id, 1 to 200 ids per request. Ids that do not exist or belong to another account are skipped; the response reports how many were deleted.","tags":["Creatives"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ids"],"properties":{"ids":{"type":"array","description":"Creative ids to delete.","minItems":1,"maxItems":200,"items":{"type":"string"}}}}}}},"responses":{"200":{"description":"How many creatives were deleted.","content":{"application/json":{"schema":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"integer","description":"Number of creatives deleted."}}}}}},"400":{"description":"Provide 1 to 200 ids.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/tools":{"get":{"operationId":"listTools","summary":"List available tools","description":"The full tool catalog: every action the API can perform, with its JSON Schema input. Tools marked destructive spend money or cannot be undone.","tags":["Tools"],"responses":{"200":{"description":"The tool catalog.","content":{"application/json":{"schema":{"type":"object","required":["tools"],"properties":{"tools":{"type":"array","items":{"$ref":"#/components/schemas/Tool"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/tools/{name}":{"parameters":[{"name":"name","in":"path","required":true,"description":"Tool name, as listed by GET /api/v1/tools.","schema":{"type":"string"}}],"post":{"operationId":"callTool","summary":"Call a tool","description":"Call a tool by name. POST the tool's arguments as the JSON body (matching its inputSchema) and receive the tool's result.","tags":["Tools"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","description":"The tool's arguments, per its inputSchema.","additionalProperties":true}}}},"responses":{"200":{"description":"The tool's result.","content":{"application/json":{"schema":{"type":"object","required":["result"],"properties":{"result":{"description":"The tool's result; shape varies per tool."}}}}}},"400":{"description":"Invalid arguments, or the tool reported an error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Unknown tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/creatives/{id}/copy":{"parameters":[{"name":"id","in":"path","required":true,"description":"Creative id.","schema":{"type":"string"}}],"get":{"operationId":"listCopy","summary":"List copy variants","description":"List copy variants for a creative, newest first.","tags":["Copy variants"],"responses":{"200":{"description":"The copy variants.","content":{"application/json":{"schema":{"type":"object","required":["copies"],"properties":{"copies":{"type":"array","items":{"$ref":"#/components/schemas/Copy"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Creative not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"upsertCopy","summary":"Create or update a copy variant","description":"Create a copy variant, or update an existing one by including its `id` in the body. Creation returns 201; both return the variant id.","tags":["Copy variants"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Include to update an existing variant."},"headline":{"type":"string","maxLength":500,"description":"Up to 500 characters."},"primaryText":{"type":"string","maxLength":5000,"description":"Up to 5000 characters."},"description":{"type":"string","maxLength":1000,"description":"Up to 1000 characters."},"label":{"type":"string","maxLength":100,"description":"Your own tag for the variant, up to 100 characters."}}}}}},"responses":{"200":{"description":"The variant was updated.","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"Copy variant id."}}}}}},"201":{"description":"The variant was created.","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"Copy variant id."}}}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Creative not found, or no copy variant with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteCopy","summary":"Delete a copy variant","description":"Delete a copy variant by id.","tags":["Copy variants"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"Copy variant id to delete."}}}}}},"responses":{"200":{"description":"The variant was deleted.","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean","enum":[true]}}}}}},"400":{"description":"Copy id required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Create an API key at https://upload.ad/dashboard/settings/api-keys and send it as a Bearer token on every request."}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthError"}}}}},"schemas":{"Account":{"type":"object","description":"The authenticated account: identity, plan, and credit balance.","required":["id","name","email","plan","credits","trialEndsAt"],"properties":{"id":{"type":"string","description":"Account id."},"name":{"type":"string","description":"Account name."},"email":{"type":"string","description":"Account email."},"plan":{"type":["string","null"],"description":"Current plan, null if not on a plan."},"credits":{"type":"integer","description":"Remaining credits."},"trialEndsAt":{"type":["string","null"],"format":"date-time","description":"Trial end date, null when on a plan."}}},"Upload":{"type":"object","description":"A background job that pushes one file to your Facebook or TikTok ad account. A new upload starts as processing; poll it until it becomes completed or failed.","required":["id","fileName","kind","sizeBytes","status","error","platform","facebook","tiktok","createdAt"],"properties":{"id":{"type":"string","description":"Upload id."},"fileName":{"type":"string","description":"Original file name."},"kind":{"type":"string","enum":["image","video"]},"sizeBytes":{"type":"integer","description":"File size in bytes."},"status":{"type":"string","enum":["processing","completed","failed"]},"error":{"type":["string","null"],"description":"Failure reason, set only on failed uploads."},"platform":{"type":"string","enum":["meta","tiktok"],"description":"Which ad account the file was pushed to."},"facebook":{"type":"object","required":["imageHash","videoId"],"properties":{"imageHash":{"type":["string","null"],"description":"Graph API image hash, set on completed Facebook image uploads."},"videoId":{"type":["string","null"],"description":"Facebook video id, set on completed Facebook video uploads."}}},"tiktok":{"type":"object","required":["imageId","videoId"],"properties":{"imageId":{"type":["string","null"],"description":"TikTok image id, set on completed TikTok image uploads."},"videoId":{"type":["string","null"],"description":"TikTok video id, set on completed TikTok video uploads."}}},"createdAt":{"type":"string","format":"date-time","description":"Creation time."}}},"Creative":{"type":"object","description":"A file in your library, created by a completed upload. Each creative can hold any number of copy variants.","required":["id","fileName","kind","contentType","status","width","height","mediaPath","copyCount","createdAt"],"properties":{"id":{"type":"string","description":"Creative id."},"fileName":{"type":"string","description":"Original file name."},"kind":{"type":"string","enum":["image","video"]},"contentType":{"type":"string","description":"MIME type."},"status":{"type":"string","enum":["importing","ready","failed"]},"width":{"type":["integer","null"],"description":"Width in pixels, null until known."},"height":{"type":["integer","null"],"description":"Height in pixels, null until known."},"mediaPath":{"type":["string","null"],"description":"Path to the media binary, for example /api/media/cr_1; fetch it with the same Bearer key. Add ?download=<filename> to receive it as an attachment. Null until the creative is ready."},"previewPath":{"type":["string","null"],"description":"Path to the 720p MP4 preview rendition for videos; null until transcoded (and always null for images)."},"copyCount":{"type":"integer","description":"Number of copy variants."},"createdAt":{"type":"string","format":"date-time","description":"Creation time."}}},"Copy":{"type":"object","description":"A set of ad texts attached to a creative. All text fields are optional, so a variant can hold as little as a single headline.","required":["id","headline","primaryText","description","label","createdAt"],"properties":{"id":{"type":"string","description":"Copy variant id."},"headline":{"type":["string","null"],"description":"Headline text."},"primaryText":{"type":["string","null"],"description":"Primary text."},"description":{"type":["string","null"],"description":"Description text."},"label":{"type":["string","null"],"description":"Your own tag for the variant."},"createdAt":{"type":"string","format":"date-time","description":"Creation time."}}},"Tool":{"type":"object","description":"A callable tool: its name, description, and JSON Schema input.","required":["name","description","inputSchema","destructive"],"properties":{"name":{"type":"string","description":"Tool name; use it in POST /api/v1/tools/{name}."},"description":{"type":"string","description":"What the tool does."},"inputSchema":{"type":"object","description":"JSON Schema for the tool's arguments.","additionalProperties":true},"destructive":{"type":"boolean","description":"True when the tool spends money or cannot be undone."}}},"Error":{"type":"object","description":"Errors raised from within a route (validation, not-found, conflict). Auth, subscription, permission, and rate-limit errors use AuthError instead.","required":["message"],"properties":{"message":{"type":"string","description":"Description of the problem."}}},"AuthError":{"type":"object","description":"Errors raised before a route runs: authentication (401), subscription (402), permission (403), and rate limiting (429).","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine-readable error code."},"message":{"type":"string","description":"Human-readable description of the problem."}}}}}}}}