CurlShip Edits API

Self-service listing changes, with proof of ownership.

GitHub OpenAPI

Operations 3

POST /api/edit-request Ask to change your listing #
GET /api/edit-request/{id} Poll an edit request #
POST /api/edit-confirm Confirm an edit request and apply it #

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/curlship-com:curlship-com-edits-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

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

curlship-com-edits-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Curlship Com Edits API
  version: 1.0.0
  contact:
    name: CurlShip Support
    email: support@curlship.com
    url: https://curlship.com
  license:
    name: Proprietary
  description: 'Operations tagged Edits across 2 of this provider''s published API definitions: curlship-com-openapi.json, curlship-com-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://curlship.com
  description: Production
tags:
- name: Edits
  description: Self-service listing changes, with proof of ownership.
paths:
  /api/edit-request:
    post:
      tags:
      - Edits
      summary: Ask to change your listing
      description: 'Starts a change; applies nothing. Prove ownership one of two ways.


        `proof: "email"` mails a one-time confirmation link to the address the listing is registered to — the response never contains the token.


        `proof: "domain"` returns a token you must publish at `/.well-known/curlship-verify.txt` (as a whole line) or as `` on **both** the currently listed site and the new one, then confirm via `/api/edit-confirm`. Publishing it only on the new domain proves you own the destination, not the listing, and is refused — as is a domain proof when the listed site is unreachable.


        Changing `email` always requires `proof: "email"`. Requests expire after 24 hours. Rate limits: 5/hour per IP, 3/day per listing.


        **Refreshing stale metadata.** Send only `listing_id` and `proof` — no `url`, no `email` — to mean "re-read the title, description and image from my page". Use it when your site''s copy changed and the directory card still shows what it said on the day you submitted. The URL is not modified, the badge is not reset, and the page scraped is always the one already on the listing. A refresh whose fetch fails, or that comes back with a bot-wall, parking or error-page title, is refused rather than written.


        Confirming a refresh takes **two** calls to `/api/edit-confirm`: the first returns `applied: false` with the proposed `changed` values and an `apply` digest and writes nothing; send that digest back to save it. The page is re-read on apply, so if it changed in between you get a fresh preview rather than a write you never saw.


        The body cannot set a title, description, or image. Those are copied from the page (`og:title`, `og:description`, `og:image`, else the title tag and the description meta). Emailing replacement copy does not change the card.


        **Tracking params on your outbound link.** A paid listing (Bronze and up) can send `tracking`, a JSON object that replaces the default `?ref=curlship` on every link to the site. Keys are `ref` or `utm_*` (lowercase letters, digits, underscores), at most 10; values are 1-100 of `A-Z a-z 0-9 . _ ~ -`. Send `{}` to clear back to `?ref=curlship`. Stored params survive a lapse and return on re-upgrade; a free listing is refused (403).


        **Removing a free listing.** Send `remove: true` with `listing_id` and `proof`, and no `url` or `email`. Confirm it the same way as any other edit. The registered address gets a 7-day undo link. A paid listing, or the listing that holds the Featured spot, is refused (409) and stays a mail to support@curlship.com, because a payment is attached. One listing per root domain: to point the same listing at a different page, send a new `url` instead of `remove`.'
      operationId: createEditRequest
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - listing_id
              properties:
                listing_id:
                  type: integer
                  example: 2142
                url:
                  type: string
                  format: uri
                  description: New product URL
                email:
                  type: string
                  format: email
                  description: New registered address (needs proof=email)
                remove:
                  type: boolean
                  description: true takes a free listing offline. Send no url and no email with it. A paid listing or the Featured holder is refused.
                tracking:
                  type: object
                  additionalProperties:
                    type: string
                  example:
                    utm_source: yours
                  description: 'Custom tracking params for the outbound link — paid tiers only (Bronze and up, 403 for free). Keys: ref or utm_*; at most 10. Values: 1-100 of A-Z a-z 0-9 . _ ~ -. An empty object clears back to ?ref=curlship.'
                proof:
                  type: string
                  enum:
                  - email
                  - domain
                  default: email
            example:
              listing_id: 2142
              url: https://new-url.example/
              proof: domain
      responses:
        '201':
          description: Request created — pending proof
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: true
                  request_id:
                    type: integer
                  proof:
                    type: string
                    enum:
                    - email
                    - domain
                  status:
                    type: string
                    example: pending
                  token:
                    type: string
                    description: Domain proof only — publish this. Never returned for email proof.
                  expires_at:
                    type: string
                  publish:
                    type: array
                    description: Domain proof only — where the token has to appear.
                    items:
                      type: object
                  message:
                    type: string
        '400':
          description: Bad body, bad URL, bad tracking params, or email change without email proof
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Custom tracking params on a free listing — a paid perk from Bronze up
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No active listing with that id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Removal refused because the listing is paid or holds the Featured spot, or the registered address has permanently rejected mail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: 'Rate limit exceeded. Retry-After is the wait in seconds. RateLimit-Policy names the window that was hit. RateLimit repeats that name with r=0 and t equal to Retry-After. Windows: submit 10 per 3600s, verify 30 per 3600s, checkout 30 per 3600s, edit 5 per 3600s per IP, edit-listing 3 per 86400s per listing.'
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
                minimum: 1
            RateLimit-Policy:
              description: 'The window that was hit. Example: "submit";q=10;w=3600.'
              schema:
                type: string
            RateLimit:
              description: 'Remaining quota. Example: "submit";r=0;t=120.'
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Confirmation email could not be sent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    servers:
    - url: https://curlship.com
      description: Production
  /api/edit-request/{id}:
    get:
      tags:
      - Edits
      summary: Poll an edit request
      description: 'Status of a request: pending, applied, expired or cancelled. Never echoes the token.'
      operationId: getEditRequest
      parameters:
      - name: id
        in: path
        required: true
        description: Edit request id
        schema:
          type: integer
          example: 7
      responses:
        '200':
          description: The request
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: true
                  request:
                    type: object
                    properties:
                      id:
                        type: integer
                      listing_id:
                        type: integer
                      proof:
                        type: string
                      status:
                        type: string
                        enum:
                        - pending
                        - applied
                        - expired
                        - cancelled
                      changes:
                        type: object
                      created_at:
                        type: string
                      expires_at:
                        type: string
        '400':
          description: Id is not an integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No request with that id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    servers:
    - url: https://curlship.com
      description: Production
  /api/edit-confirm:
    post:
      tags:
      - Edits
      summary: Confirm an edit request and apply it
      description: 'For a domain proof, this re-fetches every affected site and checks the token before writing anything. For an email proof the same thing happens when the recipient opens the mailed link and presses the confirm button (the link itself only displays the pending change — mail scanners fetch links, and the token is single use), so calling this endpoint by hand is only needed if you kept the token.


        On success: the change is written, every field is logged with its old value, the registered address is emailed an undo link valid 7 days, and a URL change resets the badge to unverified.


        **A metadata refresh takes two calls here.** The first returns `applied: false`, `status: "preview"`, the proposed `changed` values and an `apply` digest, and writes NOTHING — do not read a 200 as "done", check `applied`. Send the digest back as `apply` to save it. The page is re-read on apply and the digest re-checked, so if it changed in between you get a fresh preview instead of a write nobody saw. URL and email changes stay one-step: those values came from you and are not re-read.'
      operationId: confirmEditRequest
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - request_id
              - token
              properties:
                request_id:
                  type: integer
                  example: 7
                token:
                  type: string
                apply:
                  type: string
                  description: Metadata refresh only. The digest returned by the preview call. Omit it to get the preview; send it back to write. Ignored for url/email changes.
            example:
              request_id: 7
              token: Kx9…
      responses:
        '200':
          description: Applied, OR — for a metadata refresh sent without a matching `apply` digest — a preview that wrote nothing. Check `applied`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: true
                  applied:
                    type: boolean
                    description: false means nothing was written — this is a preview.
                  status:
                    type: string
                    enum:
                    - preview
                    description: Present only on a preview.
                  changed:
                    type: object
                    nullable: true
                    description: 'Preview only: field -> {from, to}. null when nothing would change.'
                  apply:
                    type: string
                    description: 'Preview only: send this back to write.'
                  listing_id:
                    type: integer
                    description: Preview only.
                  url:
                    type: string
                    description: 'Preview only: the page that was read.'
                  then:
                    type: object
                    description: 'Preview only: the exact follow-up call that writes.'
                  message:
                    type: string
                  listing:
                    type: object
                    properties:
                      id:
                        type: integer
                        example: 2076
                      url:
                        type: string
                        format: uri
                        example: https://yourapp.com/
                      link:
                        type: string
                        format: uri
                        example: https://yourapp.com/?ref=curlship
                        description: The URL a visitor is actually sent to — url plus tracking params. Free listings carry ?ref=curlship; paid ones carry the owner's utm_*/ref params (set via POST /api/edit-request tracking).
                      tier:
                        type: string
                        enum:
                        - free
                        - bronze
                        - silver
                        - gold
                        - platinum
                      title:
                        type:
                        - string
                        - 'null'
                        example: YourApp — ship faster
                      description:
                        type:
                        - string
                        - 'null'
                      image:
                        type:
                        - string
                        - 'null'
                        format: uri
                      has_badge:
                        type: boolean
                        description: True if a dofollow link back to curlship.com was found on the site.
                      created_at:
                        type:
                        - string
                        - 'null'
                        format: date-time
                        example: '2026-10-12T09:04:05Z'
                        description: When the listing was created, UTC.
                      category:
                        oneOf:
                        - type: object
                          properties:
                            slug:
                              type: string
                              example: developer-tools
                              description: The leaf slug when classified that deep, else the group.
                            name:
                              type: string
                              example: Developer tools
                            group:
                              type: string
                              example: developer-tools
                              description: The top-level group this listing belongs to.
                        - type: 'null'
                        description: The listing's category, or null while it is unclassified. Set by the off-server classifier — submitters cannot choose one.
        '400':
          description: Missing request_id or token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: No request with that id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: Bad token, expired, already used, or proof not satisfied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: 'Rate limit exceeded. Retry-After is the wait in seconds. RateLimit-Policy names the window that was hit. RateLimit repeats that name with r=0 and t equal to Retry-After. Windows: submit 10 per 3600s, verify 30 per 3600s, checkout 30 per 3600s, edit 5 per 3600s per IP, edit-listing 3 per 86400s per listing.'
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
                minimum: 1
            RateLimit-Policy:
              description: 'The window that was hit. Example: "submit";q=10;w=3600.'
              schema:
                type: string
            RateLimit:
              description: 'Remaining quota. Example: "submit";r=0;t=120.'
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    servers:
    - url: https://curlship.com
      description: Production
components:
  schemas:
    Error:
      type: object
      properties:
        ok:
          type: boolean
          example: false
        error:
          type: string
          example: url is required and must start with http
      required:
      - ok
      - error
externalDocs:
  description: Badge guide & instant verify
  url: https://curlship.com/badge
x-refined-from:
- curlship-com-openapi.json
- curlship-com-openapi.yml