AI Content Drop GraphQL API

Endpoint: POST https://aicontentdrop.com/graphql · SDL: /graphql/schema.graphql · Introspection: open, no credential.

The same data as the REST API, in the shape GraphQL clients expect. Public fields answer without a key; account fields and generateVideo need Authorization: Bearer acd_live_….


curl -sX POST https://aicontentdrop.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{ models(first:3){ nodes{ id creditCost } pageInfo{ hasNextPage endCursor } } }"}'

Pagination — Relay connections

Every list is a Connection. edges carry cursor and node; pageInfo carries hasNextPage, hasPreviousPage, startCursor, and endCursor; totalCount ignores pagination. Both directions work: first/after pages forward, last/before pages backward. Cursors are opaque — pass them back, do not parse them.


{
  articles(first: 10, after: "YWNkOjk") {
    totalCount
    edges { cursor node { slug title publishedAt } }
    pageInfo { hasNextPage endCursor }
  }
}

Errors — typed, in the payload

Request-level failures (bad syntax, missing credential, rate limit) come back in the standard top-level errors array with a machine code in extensions.code.

Failures a caller can act on come back inside the payload as values implementing the UserError interface, so you branch on a type instead of matching a message:


mutation {
  generateVideo(input: { prompt: "a cat surfing", model: "kling_3_0" }) {
    job { id status pollIntervalSeconds }
    errors {
      __typename
      code
      message
      ... on InsufficientCreditsError { required available topUpUrl }
      ... on AuthenticationError { createKeyUrl }
      ... on ValidationError { path received }
    }
  }
}

Concrete types: ValidationError, AuthenticationError, InsufficientCreditsError, SafetyError, NotFoundError, ProviderError. Every one carries an ErrorCode enum value, which stays stable even when the message is reworded.

Long-running work — async jobs

A generation takes one to four minutes, so the mutation does not wait for it. generateVideo returns a GenerationJob immediately with status: GENERATING and a pollIntervalSeconds; you re-query job(id:) until the status is terminal.


query Poll($id: ID!) {
  job(id: $id) {
    status
    creditsUsed
    videoUrl
    errorMessage
    pollIntervalSeconds
  }
}

pollUrl on the job is the REST equivalent, for clients mixing protocols.

Or subscribe instead of polling

generationProgress(id:) streams the same record over Server-Sent Events, in graphql-sse *distinct-connections* mode: one request carries one operation, the server writes event: next frames, and closes with event: complete. There is no WebSocket endpoint.


curl -N -X POST https://aicontentdrop.com/graphql \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -H "Authorization: Bearer acd_live_YOUR_KEY" \
  -d '{"query":"subscription { generationProgress(id: \"VIDEO_ID\") { status creditsUsed videoUrl } }"}'

event: next
data: {"data":{"generationProgress":{"status":"GENERATING","creditsUsed":0,"videoUrl":null}}}

event: next
data: {"data":{"generationProgress":{"status":"COMPLETED","creditsUsed":27,"videoUrl":"https://…"}}}

event: complete
data:

An EventSource cannot set a request body, so the same operation is reachable as a GET: https://aicontentdrop.com/graphql/stream?query=…. Queries and mutations are legal on the stream transport too and produce exactly one next — a client that only speaks SSE never needs a second code path.

Nothing is more current on the stream than in a poll: the subscription is the poll, run on our side, and it completes on its own after ten minutes so a forgotten client cannot hold a connection open. Polling job(id:) is the simpler contract and is equally supported.

Versioning and deprecation policy

The current major schema is additive-only. A field is never removed or retyped in place:

  1. A field being retired is marked @deprecated with a reason naming its replacement.
  2. It receives at least 180 days between its announcement and the earliest date it may be omitted from a future major schema.
  3. The retirement is announced on this page before it happens.
  4. A change that cannot be made additively ships as a new endpoint (/graphql/v2), never as a silent change to this one.

A deprecated element remains available on /graphql while this major endpoint is supported. The date in the register is therefore not an in-place deletion date: it is the earliest that a later major schema may omit the element. If the /graphql endpoint itself is ever retired, its HTTP responses will carry Deprecation, Sunset, and Link: rel="deprecation" headers with the same minimum 180-day notice.

Introspection hides what it deprecates. __Type.fields takes includeDeprecated and it defaults to false, so a standard introspection response simply omits Model.credits — and a client reading that response cannot tell a retiring field from one that never existed. Two ways round it:


# 1. ask introspection for them explicitly
{ __type(name: "Model") { fields(includeDeprecated: true) { name isDeprecated deprecationReason } } }

# 2. or read the register, which is derived from the live schema
{ apiInfo { deprecations { path kind reason } } }

Currently deprecated (all remain operational on this major schema):

Element Replacement Announced Earliest future-major removal Why
Model.credits Model.creditCost 2026-08-26 2027-02-22 Renamed for clarity.
JobStatus.PENDING JobStatus.GENERATING 2026-08-26 2027-02-22 Merged into GENERATING; a job is never reported as PENDING.
Query.generation Query.job 2026-08-26 2027-02-22 Renamed to make the asynchronous polling contract explicit.

Cost and limits

The standard read tier is 120 requests per minute per IP. A valid acd_agent_ read token selects 600 requests per minute for that IP without granting account access; a user acd_live_ API key authorizes account operations but does not change the read tier. RateLimit-* headers appear on every response and a 429 carries Retry-After. Query rateLimit { limit standardLimit registeredAgentLimit tier bucket windowSeconds } for the current caller and both ceilings. Queries are capped at 8,000 characters and batched operations are refused — send one operation per request.