> ## Documentation Index
> Fetch the complete documentation index at: https://www.ayrshare.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Using the GraphQL API

> Find operations, understand argument types and enums, create posts, and upload media through the Ayrshare GraphQL API.

## Finding an operation

The schema is authoritative for the supported GraphQL subset: operation roots, names, arguments, input types, and enum values. Descriptions provide usage guidance, and the [GraphQL Explorer](https://app.ayrshare.com/graphql-explorer) exposes both. For REST-only capabilities, use the [REST API reference](/docs/apis/overview).

Operation names follow the REST endpoints they reach, in camelCase. `GET /history` is `postHistory`, `GET /analytics/social` is `socialAnalytics`, `POST /post` is `createPost`.

## Queries read, mutations write

Queries are reads and validations that change nothing on our side. Mutations publish or change state, start work, or send email. Neither the REST HTTP verb nor the cost of a call decides the root:

<ul class="custom-bullets">
  <li><code>validatePost</code> checks a post without publishing it.</li>
  <li><code>validateMedia</code> checks that a media URL is reachable.</li>
  <li><code>generatePost</code> is a query because nothing on our side changes. It still counts as an API call and returns different text each time, so make sure a client cache or automatic refetch does not repeat it without you noticing.</li>
  <li><code>mediaUploadUrl</code> is a mutation because it creates an upload URL for your account.</li>
  <li><code>linkAnalytics</code> is a mutation because it can request an emailed report.</li>
  <li><code>userBatch</code> is a mutation because it starts an export job.</li>
</ul>

Use the Explorer or schema to confirm the root for each supported operation.

## Creating a post

`createPost` takes a single `input` argument, so the whole post is one object:

```graphql theme={"system"}
mutation {
  createPost(input: {
    post: "Hello from GraphQL"
    platforms: [LINKEDIN]
    idempotencyKey: "post-2026-09-01-001"
  }) {
    status
    id
  }
}
```

The input mirrors the documented [REST post endpoint](/docs/apis/post/post), including per-network option objects and the REST forms that accept more than one JSON shape:

```graphql theme={"system"}
mutation {
  createPost(input: {
    post: "New video"
    platforms: [YOUTUBE]
    mediaUrls: ["https://example.com/video.mp4"]
    idempotencyKey: "youtube-video-001"
    youTubeOptions: {
      title: "My video"
      visibility: PUBLIC
    }
  }) {
    status
    id
    postIds { platform postUrl }
  }
}
```

A post can succeed on one network and fail on another. That is not a request failure — see [Errors](/docs/apis/graphql/errors#partial-success-on-multi-network-posts).

## Argument types

Most arguments are ordinary strings, numbers and booleans. Three cases are worth knowing about.

### Enums

Many string arguments with a closed supported set are GraphQL enums, written **unquoted and upper case**:

```graphql theme={"system"}
{ socialAnalytics(platforms: [INSTAGRAM, TIKTOK]) }
```

Not `"instagram"`. The server maps each accepted enum to the exact value expected by the underlying REST controller — often, but not always, a lowercase string. An invalid enum is rejected during GraphQL validation before the operation runs, so a typo costs you nothing.

Some arguments share a name across operations but accept different values, because the endpoints genuinely differ. `reviews(platform:)` accepts only `GMB` and `FACEBOOK`, since those are the only networks with reviews. Your client's autocomplete will show the correct set for each operation.

### The JSON scalar

A few arguments are typed as `JSON` rather than a specific type. This happens where a value legitimately has more than one shape and no single GraphQL type could describe it honestly:

<ul class="custom-bullets">
  <li><code>explainError(code:)</code> accepts <code>215</code> or <code>"215"</code>, because customers hold error codes as both.</li>
  <li><code>createPost(input:)</code> uses JSON for fields such as <code>post</code> and <code>mediaUrls</code>, which can be shared values or per-platform objects.</li>
  <li><code>createAutomation(triggers:, actions:)</code> take arrays whose fields depend on each entry's <code>type</code>.</li>
  <li><code>boostFacebookPost(interests:)</code> accepts Meta interest ids as strings or numbers.</li>
</ul>

Pass the same value you would send in the corresponding REST body. A `JSON` argument is not a loophole: the resolver validates it before dispatch, so an invalid value returns a validation error and consumes no API call.

### Optional arguments and nulls

For optional arguments and optional fields inside input objects, explicit `null` is normalized to omission. Null members inside lists are preserved when the list type permits them.

## Reading responses

Most operations return a `JSON` scalar containing the complete REST response envelope, so select the root field without subfields. `createPost` currently returns a typed `PostResult`, so select its fields:

```graphql theme={"system"}
mutation {
  createPost(input: {
    post: "Hi"
    platforms: [LINKEDIN]
    idempotencyKey: "linkedin-hi-001"
  }) {
    status
    id
    postIds { platform postUrl }
    errors { platform message code }
  }
}
```

Typed response types such as `PostResult` also include `raw: JSON!`, containing the complete unmodified REST response:

```graphql theme={"system"}
mutation {
  createPost(input: {
    post: "Hi"
    platforms: [LINKEDIN]
    idempotencyKey: "linkedin-hi-001"
  }) {
    status
    raw
  }
}
```

`raw` is permanent and is there so a new field appearing in the REST response is never unreachable from GraphQL while we catch up on typing it. If you need something the typed fields do not expose, ask for `raw`.

## Uploading media

**Media bytes cannot travel through a GraphQL request.** A GraphQL request is a single JSON document under a 64 KB limit, so there is no field that accepts a file, and encoding an image as base64 inside the query would exceed that limit for anything larger than a thumbnail.

The supported path avoids the problem entirely, and is faster than uploading through an API in any case, because your bytes go straight to storage:

1. Ask for an upload URL:

   ```graphql theme={"system"}
   mutation { mediaUploadUrl(contentType: "image/jpeg") }
   ```

2. Read `data.mediaUploadUrl.uploadUrl`, `accessUrl`, and `contentType` from the returned JSON.

3. `PUT` your file **directly to `uploadUrl`**, not to the Ayrshare API, setting the request's `Content-Type` header to the returned `contentType`.

4. After the upload succeeds, pass `accessUrl` in `createPost.input.mediaUrls`:

   ```graphql theme={"system"}
   mutation {
     createPost(input: {
       post: "With a photo"
       platforms: [INSTAGRAM]
       mediaUrls: ["https://the-returned-access-url"]
       idempotencyKey: "instagram-photo-001"
     }) {
       status
       postIds { platform postUrl }
     }
   }
   ```

Treat `uploadUrl` as a short-lived write credential and do not log or expose it. `accessUrl` is the media URL used when creating the post.

You can also keep using the [REST upload endpoints](/docs/apis/media/upload-media) and reference the resulting URLs from GraphQL. The two interfaces share the same media library.

## Read next

<ul class="custom-bullets">
  <li>[Errors](/docs/apis/graphql/errors) — HTTP status codes, error shapes, and partial success.</li>
  <li>[Limits and Billing](/docs/apis/graphql/limits) — query size caps and how requests are counted.</li>
</ul>
