placementapi-com Content API

Articles for placements where the buyer supplies or commissions copy.

Operations 3

POST /v1/orders/{order_id}/content Send the article #
GET /v1/orders/{order_id}/content Every version sent #
POST /v1/orders/{order_id}/content/papi Hand the article to Placement Papi now #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/placementapi-com:placementapi-com-content-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

placementapi-com-content-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Placement Content API
  version: 1.0.0
  description: 'Normalised backlink placement inventory, deduplicated across multiple suppliers, with independent verification, held quotes, ordering, content handling and post-placement outcome tracking. Every price is the buyer price in credits (1 credit = 1 US cent), already normalised from supplier currencies, so the figure you plan with is the figure you are charged. Supplier identity is never exposed.


    Authenticate with a `pk_live_…` API key as a Bearer token. Keys carry scopes: `read` (catalogue, quotes, account reads) and `order` (anything that moves money). An MCP server exposing the same operations as tools is available at https://api.placementapi.com/mcp with the same Bearer key; the tools call these endpoints, so pricing and verification are identical either way. Unknown query parameters and brief fields are refused with 400 rather than ignored.'
  contact:
    email: hello@placementapi.com
  termsOfService: https://placementapi.com/terms
servers:
- url: https://api.placementapi.com
security:
- apiKey: []
tags:
- name: Content
  description: Articles for placements where the buyer supplies or commissions copy.
paths:
  /v1/orders/{order_id}/content:
    post:
      tags:
      - Content
      operationId: submit_content
      summary: Send the article
      description: 'For an awaiting_content or content_revision_requested order. Validated against the publisher''s spec; 422 lists the problems. `status: "draft"` stores a version without submitting (nothing charged, order status unchanged) — Papi''s draft for approval; resend with `status: "submitted"` to approve. A submission moves the order to `ordered` for the next batch. Nothing further is charged; the papi_written fee was taken with the order. Requires the `order` scope.'
      parameters:
      - name: order_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
              - $ref: '#/components/schemas/ContentBody'
              - type: object
                properties:
                  status:
                    type: string
                    enum:
                    - draft
                    - submitted
                    default: submitted
                  submitted_by:
                    type: string
                    enum:
                    - customer
                    - papi
                    default: customer
      responses:
        '201':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  order_id:
                    type: string
                  version:
                    type: integer
                  content_status:
                    type: string
                    enum:
                    - draft
                    - submitted
                  order_status:
                    $ref: '#/components/schemas/OrderStatus'
                  charged_credits:
                    type: integer
                  validation:
                    $ref: '#/components/schemas/ContentValidation'
                  next:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: A safety-net papi_written charge could not be covered; carries `validation`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '`order_not_found`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: '`content_not_expected` (publisher or supplier writes), `order_not_open`, `content_already_submitted`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: '`content_invalid`, with `validation`. Send status ''draft'' to store it anyway.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          $ref: '#/components/responses/Unavailable'
    get:
      tags:
      - Content
      operationId: get_order_content
      summary: Every version sent
      description: All versions with their validation and any supplier rejection note (vendor_note), plus the spec and rules.
      parameters:
      - name: order_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  order_id:
                    type: string
                  order_status:
                    $ref: '#/components/schemas/OrderStatus'
                  content_mode:
                    type: string
                    enum:
                    - publisher
                    - service
                    - supplied
                    - papi_written
                  spec:
                    $ref: '#/components/schemas/ContentSpec'
                  rules:
                    type: array
                    items:
                      type: string
                  versions:
                    type: array
                    items:
                      type: object
                      properties:
                        version:
                          type: integer
                        status:
                          type: string
                          description: draft | submitted | rejected | superseded
                        submitted_by:
                          type: string
                        title:
                          type:
                          - string
                          - 'null'
                        body_markdown:
                          type:
                          - string
                          - 'null'
                        body_html:
                          type:
                          - string
                          - 'null'
                        doc_url:
                          type:
                          - string
                          - 'null'
                        meta_description:
                          type:
                          - string
                          - 'null'
                        word_count:
                          type:
                          - integer
                          - 'null'
                        validation:
                          $ref: '#/components/schemas/ContentValidation'
                        vendor_note:
                          type:
                          - string
                          - 'null'
                        created_at:
                          type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '`order_not_found`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          $ref: '#/components/responses/Unavailable'
  /v1/orders/{order_id}/content/papi:
    post:
      tags:
      - Content
      operationId: write_it_for_me
      summary: Hand the article to Placement Papi now
      description: 'For an awaiting_content or content_revision_requested order: charges the 1,500-credit tier once (nothing if a writing tier was already paid, or if already papi_written) and returns the order to awaiting_content at version 0 for Papi to draft. Requires the `order` scope.'
      parameters:
      - name: order_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  order_id:
                    type: string
                  status:
                    type: string
                  content_mode:
                    type: string
                  charged_credits:
                    type: integer
                  note:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: Balance cannot cover the fee; carries price_credits.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '`order_not_found`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: '`order_not_open`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          $ref: '#/components/responses/Unavailable'
components:
  schemas:
    ContentValidation:
      type: object
      description: Will this pass the publisher? Word count against the spec, external links against the allowance, the target linked exactly once with the anchor the order names, no forbidden anchor type.
      properties:
        ok:
          type: boolean
        word_count:
          type:
          - integer
          - 'null'
        links_found:
          type: integer
        target_link_found:
          type: boolean
        problems:
          type: array
          items:
            type: string
        warnings:
          type: array
          items:
            type: string
    ContentSpec:
      type:
      - object
      - 'null'
      description: The brief to write against when the buyer supplies copy. Null when copy cannot be supplied.
      properties:
        word_count_min:
          type:
          - integer
          - 'null'
        word_count_max:
          type:
          - integer
          - 'null'
        links_allowed:
          type: integer
        anchor_types_forbidden:
          type: array
          items:
            type: string
        rules:
          type: array
          items:
            type: string
          description: Editorial rules in the publisher's own words.
    ContentBody:
      type: object
      description: An article. Send body_markdown, body_html or doc_url. A doc_url alone cannot be validated and is accepted with a warning.
      properties:
        title:
          type:
          - string
          - 'null'
          maxLength: 300
        body_markdown:
          type:
          - string
          - 'null'
          maxLength: 200000
        body_html:
          type:
          - string
          - 'null'
          maxLength: 400000
        doc_url:
          type:
          - string
          - 'null'
          format: uri
          maxLength: 2000
        meta_description:
          type:
          - string
          - 'null'
          maxLength: 400
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: string
          description: Machine-readable code, snake_case.
        detail:
          description: Human-readable explanation, or a zod flatten() object on validation failures.
      additionalProperties: true
    OrderStatus:
      type: string
      enum:
      - ordered
      - awaiting_content
      - content_revision_requested
      - awaiting_operator
      - submitted_to_vendor
      - delivered
      - cancelled
      - refunded
      - failed
  responses:
    Unavailable:
      description: '`server_misconfigured` when the store is not configured; `upstream_unfunded` when the measurement provider''s prepaid balance is exhausted (fresh profiles, gap maps and SERPs pause; cached results and catalogue are unaffected); `upstream_unavailable` on a provider error; `payments_unavailable` when card payments are not configured.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: 'Missing, malformed or revoked Bearer credential: `{ error: "unauthorized" }`.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    BadRequest:
      description: Malformed request. `error` is `bad_request` (with a zod `detail`), `invalid_json`, or `unknown_parameter` / `unknown_field` (with `unknown[]` and `accepted[]`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: '`insufficient_scope` when the key lacks the `order` scope; `customer_key_required` when the operator token is used where a customer must be attributable.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: 'Customer API key (`pk_live_…`) in `Authorization: Bearer <key>`. Issue from POST /v1/keys or the dashboard. Endpoints marked session-only reject API keys.'