Agent API
Give MemeDrop one piece of context and get a finished, captioned meme. The private-beta API uses a small authenticated request, retry-safe idempotency, and durable media that expires after 30 days.
Quickstart
The production base URL is https://api.memedrop.moyezrabbani.dev. Only input is required in the JSON body; authentication and an idempotency key are required headers.
curl --request POST https://api.memedrop.moyezrabbani.dev/api/v1/memes/generate \
--header "Authorization: Bearer $MEMEDROP_API_KEY" \
--header "Idempotency-Key: reply-20260824-001" \
--header "Content-Type: application/json" \
--data '{
"input": "We deployed on Friday and immediately broke checkout"
}'A success has status: "ok". When no verified template can be rendered, the API returns HTTP 200 with status: "no_fit" and an empty memes array.
Request contract
Send POST /api/v1/memes/generate with:
| Header | Rules |
|---|---|
Authorization | Required. Bearer <issued credential>. Legacy install IDs are not agent authentication. |
Idempotency-Key | Required. 1–200 visible, non-whitespace characters. Use a new value for each intended generation and preserve it for retries of that exact body. |
Content-Type | Required. application/json. |
| JSON field | Type | Rules |
|---|---|---|
input | string | Required. Whitespace is trimmed; 1–12,000 characters. |
options.direction | string | Optional creative steering. Whitespace is trimmed; 1–280 characters. |
options.count | integer | Optional. Defaults to 1; accepts 1–5. |
Unknown fields are rejected. The source input remains canonical; creative direction cannot override catalog, safety, placement, or caption-length constraints.
Response and media
{
"status": "ok",
"memes": [
{
"id": "a_23456789ABCD",
"image_url": "https://api.memedrop.moyezrabbani.dev/api/v1/memes/assets/a_23456789ABCD",
"expires_at": "2026-09-23T12:00:00Z"
}
]
}| Field | Meaning |
|---|---|
status | "ok" or "no_fit". |
memes | A bounded array of 1–5 assets, or an empty array for no-fit. |
memes[].id | A compact opaque asset ID beginning with a_ followed by 12 Base58 characters. |
memes[].image_url | An absolute HTTPS URL on the MemeDrop API origin. |
memes[].expires_at | The asset's 30-day expiry timestamp. |
Media is private. Fetch image_url with the same user's Bearer credential; generic object paths do not serve generated agent images.
curl --output meme.webp \
--header "Authorization: Bearer $MEMEDROP_API_KEY" \
"https://api.memedrop.moyezrabbani.dev/api/v1/memes/assets/a_23456789ABCD"Credits and idempotent replay
- A new generation reserves the requested
countof credits before provider work begins. - No-fit, provider, rendering, storage, cancellation, and persistence failures refund the full reservation. A successful request costs one credit per durable returned meme; any unused reservation is refunded.
- Repeating the same body and
Idempotency-Keyreturns the existing terminal result without generating or charging again. Reusing that key with a different body returnsidempotency_conflict. - A replay while work is active returns
idempotency_in_progress. A successful replay after its media expires returnsasset_expired.
Stable errors
Machine errors use this JSON envelope:
{
"error": {
"code": "insufficient_credits"
}
}| HTTP | Codes | Action |
|---|---|---|
| 400 | invalid_input | Correct the headers or body; do not retry unchanged. |
| 401 | authentication_failed, install_auth_not_supported | Use a valid operator-issued Bearer credential. |
| 402 | insufficient_credits | Ask the private-beta operator to grant more credits. |
| 409 | idempotency_conflict, idempotency_in_progress | Fix a conflict, or briefly wait and poll the in-progress request. |
| 429 | rate_limited | Back off with jitter before retrying the same request and key. |
| 500 | render_failure, storage_failure, asset_persistence_failure, internal_failure | The full reservation is refunded. The same key replays the terminal error. |
| 504 | provider_timeout | The full reservation is refunded. Back off; a new attempt requires a new key. |
| 404 / 410 | asset_not_found, asset_expired | Stop fetching that media URL. |
Timeouts, rate limits, and retries
Use a client timeout of at least 30 seconds. If the connection outcome is unknown, retry the exact validated body with the same idempotency key. This recovers a completed response without a second charge. Do not create a new key merely because a client timed out.
For idempotency_in_progress or rate_limited, use bounded exponential backoff with jitter and keep the same key. A terminal provider or generation failure is replayed under its original key; use a new key only when you intentionally want a new generation attempt.
Client examples
TypeScript
const idempotencyKey = crypto.randomUUID();
const response = await fetch("https://api.memedrop.moyezrabbani.dev/api/v1/memes/generate", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.MEMEDROP_API_KEY,
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
input: "We deployed on Friday and immediately broke checkout",
options: { direction: "dry and self-aware", count: 1 },
}),
signal: AbortSignal.timeout(30_000),
});
const result = await response.json();
if (response.ok && result.status === "ok") {
console.log(result.memes[0].image_url);
}Python
import os
import uuid
import requests
response = requests.post(
"https://api.memedrop.moyezrabbani.dev/api/v1/memes/generate",
headers={
"Authorization": f"Bearer {os.environ['MEMEDROP_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={"input": "We deployed on Friday and immediately broke checkout"},
timeout=30,
)
result = response.json()
if response.ok and result["status"] == "ok":
print(result["memes"][0]["image_url"])