{
  "openapi": "3.1.0",
  "info": {
    "title": "MentionDock Creator Data API",
    "version": "2026-11-12",
    "description": "Instagram and TikTok creator data from MentionDock (Pietra). Find creators by topics (niche, keyword or hashtag) with an optional follower range, or look up one exact handle. Search is asynchronous: search_creators returns PENDING, then poll get_request until SUCCEEDED or FAILED. Each delivered creator costs 2 MentionDock credits; requests that return no creators cost nothing. Human-readable docs: https://api.mentiondock.kv.cm/"
  },
  "servers": [
    {
      "url": "https://exapi.pietrastudio.com/social-data"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "creators",
      "description": "MentionDock creator search and results"
    },
    {
      "name": "account",
      "description": "MentionDock credit balance"
    }
  ],
  "paths": {
    "/balance": {
      "get": {
        "operationId": "get_balance",
        "tags": [
          "account"
        ],
        "summary": "Check MentionDock credits (after payment only).",
        "description": "Returns availableCredits; each delivered creator costs 2. Call once after payment to confirm the key works. Never show the key or headers.",
        "responses": {
          "200": {
            "description": "Balance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (\"Sign in to MentionDock.\")"
          }
        }
      }
    },
    "/creators/search": {
      "post": {
        "operationId": "search_creators",
        "tags": [
          "creators"
        ],
        "summary": "Start a creator search (after payment only; async).",
        "description": "Body: id = new \"mdrq-\"+24 lowercase hex per search; query = {topics:[1-3 terms], platform, limit, minFollowers?, maxFollowers?} or {handle, platform, limit:1} (one per handle) or {afterRequestId} (next page). limit = creators still owed, never more. Max 4 searches per order. Returns PENDING (normal): then poll get_request. Page only if still owed >= first limit, else new search with limit = still owed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              },
              "examples": {
                "topics": {
                  "summary": "Topic search with a follower range",
                  "value": {
                    "id": "mdrq-5f2c9a01b7e34d6c8a90f1e2",
                    "query": {
                      "topics": [
                        "skincare",
                        "skin care routine"
                      ],
                      "platform": "tiktok",
                      "limit": 10,
                      "minFollowers": 10000,
                      "maxFollowers": 100000
                    }
                  }
                },
                "handle": {
                  "summary": "Exact handle lookup",
                  "value": {
                    "id": "mdrq-0e344b5900aa11bb22cc33dd",
                    "query": {
                      "handle": "natgeo",
                      "platform": "instagram",
                      "limit": 1
                    }
                  }
                },
                "nextPage": {
                  "summary": "Next page of an earlier topic search",
                  "value": {
                    "id": "mdrq-9b3d1e7c681113b232a2156f",
                    "query": {
                      "afterRequestId": "mdrq-5f2c9a01b7e34d6c8a90f1e2"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted. Poll get_request with the same id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestStatus"
                }
              }
            }
          },
          "201": {
            "description": "Accepted. Poll get_request with the same id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestStatus"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query, e.g. {\"message\":\"Choose a username or topics.\"}",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        }
      }
    },
    "/requests/{id}": {
      "get": {
        "operationId": "get_request",
        "tags": [
          "creators"
        ],
        "summary": "Get search status and creators.",
        "description": "Always pass includePosts=true (recent post links/dates are part of the deliverable). Poll every 10 s, up to 3 min. PENDING/RUNNING: wait. SUCCEEDED: creators in result.items; hasMore means a next page exists. FAILED: wait 60 s and retry once with a new search id, then tell the customer. Deliver only returned rows; never invent creators.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The request ID sent to search_creators (mdrq- + 24 lowercase hex).",
            "schema": {
              "type": "string",
              "pattern": "^mdrq-[0-9a-f]{24}$"
            }
          },
          {
            "name": "includePosts",
            "in": "query",
            "required": false,
            "description": "Always true: include up to 5 recent posts per creator (link, date, caption).",
            "schema": {
              "type": "boolean",
              "default": true
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Request status and, when SUCCEEDED, the creators.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestResult"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "404": {
            "description": "Unknown request ID for this account"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "MentionDock API key (starts with md_key_). Sent as Authorization: Bearer <key>. Keys are issued by Pietra/MentionDock; there is no self-serve key page."
      }
    },
    "schemas": {
      "SearchRequest": {
        "type": "object",
        "required": [
          "id",
          "query"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^mdrq-[0-9a-f]{24}$",
            "description": "New request ID you create: \"mdrq-\" + 24 lowercase hex characters. Use a new ID for every new search; re-send the same ID only to retry a submission whose response was lost."
          },
          "query": {
            "type": "object",
            "description": "Exactly one of: topics, handle, or afterRequestId (afterRequestId must be the only field). Unknown fields are rejected.",
            "properties": {
              "topics": {
                "type": "array",
                "items": {
                  "type": "string",
                  "maxLength": 200
                },
                "description": "Ordered list of 1-3 related search terms (niche, keyword or hashtag; a leading # is removed). The server tries later terms when it needs more creators."
              },
              "handle": {
                "type": "string",
                "maxLength": 100,
                "description": "One exact username such as natgeo or @natgeo (letters, digits, . and _). Requires limit 1."
              },
              "platform": {
                "type": "string",
                "enum": [
                  "instagram",
                  "tiktok"
                ],
                "description": "Platform to search."
              },
              "limit": {
                "type": "integer",
                "minimum": 1,
                "maximum": 50,
                "description": "Creators still owed for this order (1-50; 1 for handle). Never more than the customer paid for."
              },
              "minFollowers": {
                "type": "integer",
                "minimum": 0,
                "description": "Optional lower follower bound (topics searches)."
              },
              "maxFollowers": {
                "type": "integer",
                "minimum": 0,
                "description": "Optional upper follower bound (topics searches)."
              },
              "afterRequestId": {
                "type": "string",
                "pattern": "^mdrq-[0-9a-f]{24}$",
                "description": "Next page of an earlier SUCCEEDED topics request whose hasMore was true. Inherits its topics, platform, follower range and limit. Send it as the only query field."
              }
            }
          }
        }
      },
      "RequestStatus": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "RUNNING",
              "SUCCEEDED",
              "FAILED"
            ]
          }
        }
      },
      "RequestResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "RUNNING",
              "SUCCEEDED",
              "FAILED"
            ]
          },
          "requestedRows": {
            "type": "integer",
            "description": "The limit that was asked for."
          },
          "returnedRows": {
            "type": "integer",
            "description": "Creators actually returned."
          },
          "shortfallRows": {
            "type": "integer",
            "description": "requestedRows - returnedRows."
          },
          "costCredits": {
            "type": "integer",
            "description": "Credits charged for this request (2 per returned creator)."
          },
          "hasMore": {
            "type": "boolean",
            "description": "More stored creators may match; fetch them with query {afterRequestId: this id}."
          },
          "missingCreatorIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "discoveryCoverage": {
            "type": "string",
            "description": "\"bounded\" when the search stopped early (see discoveryNotice)."
          },
          "discoveryNotice": {
            "type": "string"
          },
          "errorCode": {
            "type": "string",
            "description": "Set when FAILED, e.g. TEMPORARILY_UNAVAILABLE."
          },
          "error": {
            "type": "string",
            "description": "Plain-English reason when FAILED."
          },
          "result": {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Creator"
                }
              }
            }
          }
        }
      },
      "Creator": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "MentionDock account ID"
          },
          "platform": {
            "type": "string",
            "enum": [
              "instagram",
              "tiktok"
            ]
          },
          "platformAccountId": {
            "type": "string"
          },
          "handle": {
            "type": "string",
            "description": "Username without @. Profile link: https://www.instagram.com/<handle>/ or https://www.tiktok.com/@<handle>"
          },
          "displayName": {
            "type": "string"
          },
          "avatarUrl": {
            "type": "string"
          },
          "followerCount": {
            "type": "string",
            "description": "Follower count as a numeric string, e.g. \"21450\". May be empty or 0 when unknown. There is no observed-at date."
          },
          "bio": {
            "type": [
              "string",
              "null"
            ]
          },
          "relevance": {
            "description": "Present on topics searches only."
          },
          "recentPosts": {
            "type": "array",
            "description": "Only with includePosts=true; up to 5. No like, view or comment counts.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "platformContentId": {
                  "type": "string"
                },
                "publishedAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": {
                  "type": "string"
                },
                "contentUrl": {
                  "type": "string"
                },
                "thumbnailUrl": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Balance": {
        "type": "object",
        "properties": {
          "availableCredits": {
            "type": "integer"
          },
          "remainingCredits": {
            "type": "integer"
          },
          "reservedCredits": {
            "type": "integer"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          },
          "error": {
            "type": "string"
          },
          "statusCode": {
            "type": "integer"
          }
        }
      }
    }
  }
}
