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:

HeaderRules
AuthorizationRequired. Bearer <issued credential>. Legacy install IDs are not agent authentication.
Idempotency-KeyRequired. 1–200 visible, non-whitespace characters. Use a new value for each intended generation and preserve it for retries of that exact body.
Content-TypeRequired. application/json.
JSON fieldTypeRules
inputstringRequired. Whitespace is trimmed; 1–12,000 characters.
options.directionstringOptional creative steering. Whitespace is trimmed; 1–280 characters.
options.countintegerOptional. 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"
    }
  ]
}
FieldMeaning
status"ok" or "no_fit".
memesA bounded array of 1–5 assets, or an empty array for no-fit.
memes[].idA compact opaque asset ID beginning with a_ followed by 12 Base58 characters.
memes[].image_urlAn absolute HTTPS URL on the MemeDrop API origin.
memes[].expires_atThe 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

Stable errors

Machine errors use this JSON envelope:

{
  "error": {
    "code": "insufficient_credits"
  }
}
HTTPCodesAction
400invalid_inputCorrect the headers or body; do not retry unchanged.
401authentication_failed, install_auth_not_supportedUse a valid operator-issued Bearer credential.
402insufficient_creditsAsk the private-beta operator to grant more credits.
409idempotency_conflict, idempotency_in_progressFix a conflict, or briefly wait and poll the in-progress request.
429rate_limitedBack off with jitter before retrying the same request and key.
500render_failure, storage_failure, asset_persistence_failure, internal_failureThe full reservation is refunded. The same key replays the terminal error.
504provider_timeoutThe full reservation is refunded. Back off; a new attempt requires a new key.
404 / 410asset_not_found, asset_expiredStop 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"])