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

# Create connection

> Create a connection from credentials you already hold, verified with the provider before anything is stored.

Creates a connection from credentials you already hold, without sending anyone through an OAuth flow. Use this when your systems already store the credentials, or when you connect through your own platform app.

Markifact verifies the credentials with the provider before storing anything. A successful response means the connection is live and ready to use in nodes, agents, and MCP.

## Endpoint

```http theme={"dark"}
POST /v1/connections
```

## Which types this works for

Call [List connection types](/api-reference/connections/list-connection-types) and look at `auth_methods`:

| `auth_methods`        | What it means                                                                                                                                                    |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `["manual"]`          | Only this endpoint. There is no Markifact OAuth app for the channel, so credentials are the only way in. Shopify, Ahrefs, and OpenAI Ads work this way.          |
| `["oauth", "manual"]` | Either way. Using your own app here is a [white-label connection](/core-concepts/white-label-connections), so your clients never see Markifact in their account. |
| `["oauth"]`           | Not this endpoint. Use [Create auth link](/api-reference/connections/create-auth-link).                                                                          |

The same endpoint response lists each type's `credential_fields`, which is exactly what belongs in `credentials`, and a `docs_url` pointing at a plain-markdown page that explains where each of those values comes from. Fetch that page when you need the steps for a channel.

<Note>
  Connecting through your own app on a channel where Markifact also has one requires the **Team** plan. Types that are manual only, such as Shopify, have no plan requirement beyond API access itself.
</Note>

## Request Body

| Field             | Type    | Required | Description                                                                                                                                                    |
| ----------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `connection_type` | string  | Yes      | A type whose `auth_methods` include `manual`, such as `gads` or `shopify`.                                                                                     |
| `name`            | string  | Yes      | Your label for the connection, up to 100 characters. Shown wherever connections are listed.                                                                    |
| `credentials`     | object  | Yes      | Exactly the `credential_fields` the type lists. A missing or unrecognised field is rejected before any call to the provider.                                   |
| `is_private`      | boolean | No       | Whether the connection should be private to the API-key user. Defaults to `false`, meaning shared with the workspace so other team members can see and use it. |

## Example Request

```bash theme={"dark"}
curl https://api.markifact.com/v1/connections \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_type": "gads",
    "name": "Acme Agency Google Ads",
    "credentials": {
      "client_id": "1234567890-abc123.apps.googleusercontent.com",
      "client_secret": "GOCSPX-...",
      "refresh_token": "1//0g..."
    },
    "is_private": false
  }'
```

## Response

```json theme={"dark"}
{
  "id": "1f2e3d4c-0000-0000-0000-000000000000",
  "type": "gads",
  "display_name": "Acme Agency Google Ads",
  "external_id": null,
  "is_private": false,
  "created_at": 1764086400,
  "updated_at": 1764086400
}
```

## Response Fields

| Field          | Type         | Description                                                                    |
| -------------- | ------------ | ------------------------------------------------------------------------------ |
| `id`           | string       | Markifact connection ID. Pass this as `connection_id` when running operations. |
| `type`         | string       | The connection type you created.                                               |
| `display_name` | string       | The `name` you sent.                                                           |
| `external_id`  | string, null | Always `null` here. External IDs are set when creating an auth link.           |
| `is_private`   | boolean      | Whether the connection is restricted to the API-key user.                      |
| `created_at`   | integer      | Creation time as a Unix timestamp in seconds.                                  |
| `updated_at`   | integer      | Last update time as a Unix timestamp in seconds.                               |

## Rotating credentials

Which connection a request updates depends on how the type derives its ID:

| Types                                                            | Keyed on                  | Posting the same values again                                                                                                             |
| ---------------------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| White-label channels, such as Google Ads, Meta Ads or TikTok Ads | Type and `name`           | Replaces the credentials in place. Same connection ID, so bound workflows keep running. A different `name` creates a separate connection. |
| Slack                                                            | Workspace and app         | Replaces in place whatever `name` you send.                                                                                               |
| Shopify                                                          | Store                     | Replaces in place whatever `name` you send.                                                                                               |
| Ahrefs, SEMrush, Adjust, DataForSEO, OpenAI Ads                  | Nothing, each post is new | Creates a new connection. Rebind your workflows to it, then delete the old one.                                                           |

Leave `is_private` out when replacing credentials. Omitting it keeps the connection's current visibility.

## Errors

| Status | When                                                                                                                                  |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | The type cannot be created from credentials, a credential field is missing or unrecognised, or the provider rejected the credentials. |
| `401`  | Invalid API key.                                                                                                                      |
| `403`  | The connection type is white-label capable and your team is not on the Team plan.                                                     |

Verification failures return the provider's own reason in `detail`, so you can tell an expired refresh token from an app that lacks the right scope.

```json theme={"dark"}
{
  "detail": "Google rejected the refresh token (invalid_grant). Make sure it was minted with this exact client ID, has not been revoked, and that the OAuth consent screen is published (apps left in Testing status issue refresh tokens that expire after 7 days)."
}
```

Nothing is stored when verification fails. Fix the credentials and post again.


## OpenAPI

````yaml POST /v1/connections
openapi: 3.1.0
info:
  title: Markifact API
  version: 1.0.0
  description: API for managing Markifact workspace connections.
servers:
  - url: https://api.markifact.com
security:
  - bearerAuth: []
paths:
  /v1/connections:
    post:
      tags:
        - Connections
      summary: Create connection
      description: >-
        Create a connection from credentials you already hold, for any
        connection type whose auth_methods include "manual". The credentials are
        verified with the provider before anything is stored, so a successful
        response means the connection works. OAuth-only types cannot be created
        this way; use POST /v1/connections/auth-link instead.
      operationId: createConnection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateConnectionRequest'
      responses:
        '200':
          description: Connection created and verified.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Connection'
        '400':
          description: >-
            The type cannot be created from credentials, a credential field is
            missing or unrecognised, or the provider rejected the credentials.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: White-label connections require the Team plan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    CreateConnectionRequest:
      type: object
      properties:
        connection_type:
          type: string
          example: gads
          description: >-
            A connection type whose auth_methods include "manual". See GET
            /v1/connections/types.
        name:
          type: string
          maxLength: 100
          example: Acme Agency Google Ads
          description: >-
            Your label for the connection. For white-label types, posting the
            same type and name again replaces the stored credentials in place.
            Slack and Shopify are keyed on the workspace and store instead, and
            API-key types (Ahrefs, SEMrush, Adjust, DataForSEO, OpenAI Ads)
            always create a new connection.
        credentials:
          type: object
          additionalProperties:
            type: string
          description: >-
            Exactly the credential_fields the type lists. A missing or
            unrecognised field is rejected before any call to the provider.
          example:
            client_id: 1234567890-abc123.apps.googleusercontent.com
            client_secret: GOCSPX-...
            refresh_token: 1//0g...
        is_private:
          type: boolean
          default: false
          description: >-
            Whether the connection should be private to the API-key user.
            Defaults to false, meaning shared with the workspace so other team
            members can see and use it. Omit it when replacing credentials on an
            existing connection and the current visibility is kept.
      required:
        - connection_type
        - name
        - credentials
    Connection:
      type: object
      properties:
        id:
          type:
            - string
            - 'null'
          description: Public Markifact connection ID.
        type:
          type: string
          description: Connection type.
        display_name:
          type:
            - string
            - 'null'
        external_id:
          type:
            - string
            - 'null'
        is_private:
          type: boolean
        created_at:
          type:
            - integer
            - 'null'
          format: int64
        updated_at:
          type:
            - integer
            - 'null'
          format: int64
      required:
        - id
        - type
        - is_private
    Error:
      type: object
      properties:
        detail: {}
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

````